is there a way how to create javafx.scene.control.Tooltip in thread which is not "JavaFX Application Thread"?
Because of performance I prepare some visual elements in one thread and keep them in memory. And when I need them I use Application Thread for showing them. But new Tooltip() seems to require Application Thread only.
is there a way how to create javafx.scene.control.Tooltip in thread which is not "JavaFX Application Thread"?
For JavaFX 2.2 - No.
See this JavaFX issue tracker tiecket: Tooltip of Tab (and other controls) cannot be set of FXApplicationThread.
There is a suggested workaround in the ticket to create the Tooltip in a Platform.runLater call.
package fxthread_25127_wrongthread;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Tooltip;
import javafx.stage.Stage;
/**
*
* #author mickf
*/
public class Main extends Application {
Tooltip tooltip;
public static void main(String[] args) {
Application.launch(args);
}
#Override public void init() throws Exception {
Platform.runLater(new Runnable() {
#Override public void run() {
tooltip = new Tooltip("Top tip : use runLater()");
}
});
/*
* this will cause an ISE
*/
//tooltip = new Tooltip("flip");
}
#Override public void start(Stage s) {
System.exit(0);
}
}
A description of how Platform.runLater works from it's javadoc:
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater.
Related
I am a beginner in javaFX and am stuck in this one area. Any help will be appreciated a lot.
This is sample app I have made for clear understanding using scene builder. There is a text area and a button.I want to set data into the text area on the button click. The setting happens in another thread.
The code is as follows:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class TpController{
#FXML
private ScrollPane scrollPane;
#FXML
private Button button;
#FXML
public TextArea txtArea ;
private Stage stage;
public void setTextArea(TextArea txt)
{
this.txtArea = txt ;
}
public TextArea getTextArea()
{
return txtArea;
}
public void setStage(Stage stage)
{
this.stage = stage;
}
public Stage getStage()
{
return stage;
}
public void setTopText(String text) {
// set text from another class
txtArea.setText(text);
}
public void buttonHandler()
{
tpThread t = new tpThread();
t.start();
}
The tpThread class is as follows:
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
public class tpThread extends Thread {
#Override
public void run() {
// TODO Auto-generated method stub
FXMLLoader loader = new FXMLLoader(getClass().getResource("Justtp.fxml"));
try {
Parent root = (Parent) loader.load();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
TpController myController = loader.getController();
TextArea t = myController.getTextArea();
String data = "hi\nhello\nhow are you\nnice to meet you\nhahaha";
//System.out.println(t.setData("hi"));
myController.setTopText(data);
}
Instead of using setTopText, i have also directly used
t.setText(data);
But no use. My final output does nothing on the button click.
There are many issues with your code.
Modifications to the active scene graph off of the JavaFX application thread must be performed via Platform.runLater().
You don't need another thread to accomplish something on a button click.
You can just define an action handler for the button using setOnAction().
Event handler methods on controllers can also be cross-referenced in FXML via onAction="#handleButtonAction" where handleButtonAction is defined as a public void handleButtonAction(ActionEvent event) method in your controller.
All event handlers are invoked by the JavaFX runtime on the JavaFX application thread, so you don't need to worry about multi-threading when writing event handlers - the JavaFX event handling programming model is single threaded.
Loading an FXML as you do in your code and not attaching the resultant node to a scene is pointless as the user will never see anything that is not attached to a scene.
There may be other issues with your code which cause it not to work as you expect.
In general, for assistance debugging an issue, provide an mcve. Note it should be both minimal and complete so that somebody could copy and paste the code to replicate the issue (and pretty much nothing else).
I'm making a program which reads text files. What I would like to do is show an arbitrary node (Alert or other Node) which is created in separate thread before or during the file reading. I tried using Task and Platform.runLater() like this:
if (filetoopen != null)
{
Platform.runLater(new Runnable() {
#Override
void run() {
Alert alert=new Alert(Alert.AlertType.INFORMATION)
alert.setHeaderText('TEST')
}
})
//method to read the file
Tools.convertFromFile(filetoopen,newredactor)
lastDirectory = filetoopen.getParentFile()
}
I'd like to show an Alert or progress bar of reading the file, but the Control initializes after the reading is finished. So, is it possible to show a Node with a progress bar while the file is being read? Or the Runnable I create will always be executed in the end?
Edit: an attempt with Task:
class Alerter extends Task{
Alerter(File f,Editor e)
{
file=f
editor=e
}
File file
Editor editor
#Override
protected Object call() throws Exception {
Dialog dialog=new Dialog()
DialogPane dp=dialog.getDialogPane()
dp.setHeaderText('TEST')
dp.getButtonTypes().add(new ButtonType('Cancel',ButtonBar.ButtonData.CANCEL_CLOSE))
dialog.setOnCloseRequest(new javafx.event.EventHandler<DialogEvent>() {
#Override
void handle(DialogEvent event) {
dialog.close()
}
})
dialog.show()
Tools.convertFromFile(file,editor)
return null
}
}
The dialog still initializes after Tools.convertFromFile.
There are two threading rules in JavaFX (and in almost every other UI toolkit):
Changes to the scene graph (i.e. creating new scenes or windows, or changing the state of nodes already displayed) must be done on the FX Application Thread.
Long-running processes should be performed on a background thread (i.e. not the FX Application Thread), otherwise the UI will become unresponsive.
Your first code block violates the second rule (probably, you haven't shown much context) and your second code block violates the first rule.
So basically you need to:
Show the dialog from the FX Application Thread
Start a new thread which processes the file in the background
From the new thread, schedule any changes to the new UI on the FX Application Thread
When processing the file finishes, update the UI on the FX Application Thread
You can use Platform.runLater(...) to schedule code to run on the FX Application Thread, but the Task class provides more convenient API for these updates.
So:
// set up and show dialog:
ProgressBar progressBar = new ProgressBar();
DialogPane dialogPane = new DialogPane();
dialogPane.getButtonTypes().setAll(ButtonType.OK);
dialogPane.setHeaderText("Processing file");
dialogPane.setContent(progressBar);
dialogPane.lookupButton(ButtonType.OK).setDisable(true);
Dialog dialog = new Dialog();
dialog.setDialogPane(dialogPane);
dialog.show();
// create task:
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
Tools.convertFromFile(file, editor);
// can call updateProgress(...) here to update the progress periodically
return null ;
}
};
// update progress bar with progress from task:
progressBar.progressProperty().bind(task.progressProperty());
// when task completes, update dialog:
task.setOnSucceeded(event -> {
dialogPane.lookupButton(ButtonType.OK).setDisable(false);
progressBar.progressProperty().unbind();
progressBar.setProgress(1);
dialogPane.setHeaderText("Processing complete");
});
// handles errors:
task.setOnFailed(event -> {
dialogPane.lookupButton(ButtonType.OK).setDisable(false);
progressBar.progressProperty().unbind();
progressBar.setProgress(0);
dialogPane.setHeaderText("An error occurred");
});
// run task in background thread:
Thread thread = new Thread(task);
thread.start();
Note here that your Tools.convertFromFile(...) method is called from a background thread, so it must not update the UI (or at least any calls in that method that do update the UI must be wrapped in Platform.runLater(...)).
Here is a complete SSCCE (which just sleeps as a demo of a long-running process):
import java.util.Random;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TaskWithProgressDemo extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Start process");
button.setOnAction(e -> {
button.setDisable(true);
// set up and show dialog:
ProgressBar progressBar = new ProgressBar();
DialogPane dialogPane = new DialogPane();
dialogPane.getButtonTypes().setAll(ButtonType.OK);
dialogPane.setHeaderText("Processing file in progress");
dialogPane.setContent(progressBar);
dialogPane.lookupButton(ButtonType.OK).setDisable(true);
Dialog<Void> dialog = new Dialog<Void>();
dialog.setDialogPane(dialogPane);
dialog.show();
// create task:
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
Random rng = new Random();
for (int i = 0 ; i <= 100 ; i++) {
Thread.sleep(rng.nextInt(40));
updateProgress(i, 100);
}
if (rng.nextBoolean()) {
System.out.println("Simulated error");
throw new Exception("An unknown error occurred");
}
return null ;
}
};
// update progress bar with progress from task:
progressBar.progressProperty().bind(task.progressProperty());
// when task completes, update dialog:
task.setOnSucceeded(event -> {
dialogPane.lookupButton(ButtonType.OK).setDisable(false);
button.setDisable(false);
progressBar.progressProperty().unbind();
progressBar.setProgress(1);
dialogPane.setHeaderText("Processing complete");
});
// handles errors:
task.setOnFailed(event -> {
dialogPane.lookupButton(ButtonType.OK).setDisable(false);
button.setDisable(false);
progressBar.progressProperty().unbind();
progressBar.setProgress(0);
dialogPane.setHeaderText("An error occurred");
});
// run task in background thread:
Thread thread = new Thread(task);
thread.start();
});
StackPane root = new StackPane(button);
root.setPadding(new Insets(20));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
So I finally figured it out. I had to move both my file loading code and progress update to a Task, so it wouldn't block FX thread. The indicator shows progress of loading a file.
Edit: to achieve progress display in a separate non-blocking window, must use a new Stage instead of anything else.
I'm new guy here :)
I have a small problem which concerns binding in JavaFX. I have created Task which is working as a clock and returns value which has to be set in a special label (label_Time). This label presents how many seconds left for player's answer in quiz.
The problem is how to automatically change value in label using the timer task? I tried to link value from timer Task (seconds) to label_Time value in such a way...
label_Time.textProperty().bind(timer.getSeconds());
...but it doesn't work. Is it any way to do this thing?
Thanks in advance for your answer! :)
Initialize method in Controller class:
public void initialize(URL url, ResourceBundle rb) {
Timer2 timer = new Timer2();
label_Time.textProperty().bind(timer.getSeconds());
new Thread(timer).start();
}
Task class "Timer2":
public class Timer2 extends Task{
private static final int SLEEP_TIME = 1000;
private static int sec;
private StringProperty seconds;
public Timer2(){
Timer2.sec = 180;
this.seconds = new SimpleStringProperty("180");
}
#Override protected StringProperty call() throws Exception {
int iterations;
for (iterations = 0; iterations < 1000; iterations++) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
System.out.println("TIK! " + sec);
seconds.setValue(String.valueOf(sec));
System.out.println("TAK! " + seconds.getValue());
// From the counter we subtract one second
sec--;
//Block the thread for a short time, but be sure
//to check the InterruptedException for cancellation
try {
Thread.sleep(10);
} catch (InterruptedException interrupted) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
}
}
return seconds;
}
public StringProperty getSeconds(){
return this.seconds;
}
}
Why your app does not work
What is happening is that you run the task on it's own thread, set the seconds property in the task, then the binding triggers an immediate update of the label text while still on the task thread.
This violates a rule for JavaFX thread processing:
An application must attach nodes to a Scene, and modify nodes that are already attached to a Scene, on the JavaFX Application Thread.
This is the reason that your originally posted program does not work.
How to fix it
To modify your original program so that it will work, wrap the modification of the property in the task inside a Platform.runLater construct:
Platform.runLater(new Runnable() {
#Override public void run() {
System.out.println("TIK! " + sec);
seconds.setValue(String.valueOf(sec));
System.out.println("TAK! " + seconds.getValue());
}
});
This ensures that when you write out to the property, you are already on the JavaFX application thread, so that when the subsequent change fires for the bound label text, that change will also occur on the JavaFX application thread.
On Property Naming Conventions
It is true that the program does not correspond to JavaFX bean conventions as Matthew points out. Conforming to those conventions is both useful in making the program more readily understandable and also for making use of things like the PropertyValueFactory which reflect on property method names to allow table and list cells to automatically update their values as the underlying property is updated. However, for your example, not following JavaFX bean conventions does not explain why the program does not work.
Alternate Solution
Here is an alternate solution to your countdown binding problem which uses the JavaFX animation framework rather than the concurrency framework. I prefer this because it keeps everything on the JavaFX application thread and you don't need to worry about concurrency issues which are difficult to understand and debug.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountdownTimer extends Application {
#Override public void start(final Stage stage) throws Exception {
final CountDown countdown = new CountDown(10);
final CountDownLabel countdownLabel = new CountDownLabel(countdown);
final Button countdownButton = new Button(" Start ");
countdownButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
countdownButton.setText("Restart");
countdown.start();
}
});
VBox layout = new VBox(10);
layout.getChildren().addAll(countdownLabel, countdownButton);
layout.setAlignment(Pos.BASELINE_RIGHT);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;");
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
class CountDownLabel extends Label {
public CountDownLabel(final CountDown countdown) {
textProperty().bind(Bindings.format("%3d", countdown.timeLeftProperty()));
}
}
class CountDown {
private final ReadOnlyIntegerWrapper timeLeft;
private final ReadOnlyDoubleWrapper timeLeftDouble;
private final Timeline timeline;
public ReadOnlyIntegerProperty timeLeftProperty() {
return timeLeft.getReadOnlyProperty();
}
public CountDown(final int time) {
timeLeft = new ReadOnlyIntegerWrapper(time);
timeLeftDouble = new ReadOnlyDoubleWrapper(time);
timeline = new Timeline(
new KeyFrame(
Duration.ZERO,
new KeyValue(timeLeftDouble, time)
),
new KeyFrame(
Duration.seconds(time),
new KeyValue(timeLeftDouble, 0)
)
);
timeLeftDouble.addListener(new InvalidationListener() {
#Override public void invalidated(Observable o) {
timeLeft.set((int) Math.ceil(timeLeftDouble.get()));
}
});
}
public void start() {
timeline.playFromStart();
}
}
Update for additional questions on Task execution strategy
Is it possible to run more than one Task which includes a Platform.runLater(new Runnable()) method ?
Yes, you can use multiple tasks. Each task can be of the same type or a different type.
You can create a single thread and run each task on the thread sequentially, or you can create multiple threads and run the tasks in parallel.
For managing multiple tasks, you can create an overseer Task. Sometimes it is appropriate to use a Service for managing the multiple tasks and the Executors framework for managing multiple threads.
There is an example of a Task, Service, Executors co-ordination approach: Creating multiple parallel tasks by a single service In each task.
In each task you can place no runlater call, a single runlater call or multiple runlater calls.
So there is a great deal of flexibility available.
Or maybe I should create one general task which will be only take data from other Tasks and updating a UI?
Yes you can use a co-ordinating task approach like this if complexity warrants it. There is an example of such an approach in in Render 300 charts off screen and save them to files.
Your "Timer2" class doesn't conform to the JavaFX bean conventions:
public String getSeconds();
public void setSeconds(String seconds);
public StringProperty secondsProperty();
My application is Swing-based. I would like to introduce JavaFX and configure it to render a Scene on a secondary display.
I could use a JFrame to hold a JFXPanel which could hold a JFXPanel but I would like to achieve this with JavaFX API.
Subclassing com.sun.glass.ui.Application and using Application.launch(this) is not an option because the invoking thread would be blocked.
When instantiating a Stage from Swing EDT, the error I get is:
java.lang.IllegalStateException: Toolkit not initialized
Any pointers?
EDIT: Conclusions
Problem: Non-trivial Swing GUI application needs to run JavaFX components. Application's startup process initializes the GUI after starting up a dependent service layer.
Solutions
Subclass JavaFX Application class and run it in a separate thread e.g.:
public class JavaFXInitializer extends Application {
#Override
public void start(Stage stage) throws Exception {
// JavaFX should be initialized
someGlobalVar.setInitialized(true);
}
}
Sidenote: Because Application.launch() method takes a Class<? extends Application> as an argument, one has to use a global variable to signal JavaFX environment has been initialized.
Alternative approach: instantiate JFXPanel in Swing Event Dispatcher Thread:
final CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new JFXPanel(); // initializes JavaFX environment
latch.countDown();
}
});
latch.await();
By using this approach the calling thread will wait until JavaFX environment is set up.
Pick any solution you see fit. I went with the second one because it doesn't need a global variable to signal the initialization of JavaFX environment and also doesn't waste a thread.
Found a solution. If I just create a JFXPanel from Swing EDT before invoking JavaFX Platform.runLater it works.
I don't know how reliable this solution is, I might choose JFXPanel and JFrame if turns out to be unstable.
public class BootJavaFX {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new JFXPanel(); // this will prepare JavaFX toolkit and environment
Platform.runLater(new Runnable() {
#Override
public void run() {
StageBuilder.create()
.scene(SceneBuilder.create()
.width(320)
.height(240)
.root(LabelBuilder.create()
.font(Font.font("Arial", 54))
.text("JavaFX")
.build())
.build())
.onCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent windowEvent) {
System.exit(0);
}
})
.build()
.show();
}
});
}
});
}
}
Since JavaFX 9, you can run JavaFX application without extending Application class, by calling Platform.startup():
Platform.startup(() ->
{
// This block will be executed on JavaFX Thread
});
This method starts the JavaFX runtime.
The only way to work with JavaFX is to subclass Application or use JFXPanel, exactly because they prepare env and toolkit.
Blocking thread can be solved by using new Thread(...).
Although I suggest to use JFXPanel if you are using JavaFX in the same VM as Swing/AWT, you can find more details here: Is it OK to use AWT with JavaFx?
I checked the source code and this is to initialize it
com.sun.javafx.application.PlatformImpl.startup(()->{});
and to exit it
com.sun.javafx.application.PlatformImpl.exit();
I used following when creating unittests for testing javaFX tableview updates
public class testingTableView {
#BeforeClass
public static void initToolkit() throws InterruptedException
{
final CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(() -> {
new JFXPanel(); // initializes JavaFX environment
latch.countDown();
});
if (!latch.await(5L, TimeUnit.SECONDS))
throw new ExceptionInInitializerError();
}
#Test
public void updateTableView() throws Exception {
TableView<yourclassDefiningEntries> yourTable = new TableView<>();
.... do your testing stuff
}
}
even though this post is not test related, then it helped me to get my unittest to work
without the BeforeClass initToolkit, then the instantiation of TableView in the unittest would yield a message of missing toolkit
There's also way to initialize toolkit explicitly, by calling:
com.sun.javafx.application.PlatformImpl#startup(Runnable)
Little bit hacky, due to using *Impl, but is useful, if you don't want to use Application or JXFPanel for some reason.
re-posting myself from this post
private static Thread thread;
public static void main(String[] args) {
Main main = new Main();
startup(main);
thread = new Thread(main);
thread.start();
}
public static void startup(Runnable r) {
com.sun.javafx.application.PlatformImpl.startup(r);
}
#Override
public void run() {
SoundPlayer.play("BelievexBelieve.mp3");
}
This is my solution. The class is named Main and implements Runnable. Method startup(Runnable r) is the key.
Using Jack Lin’s answer, I found that it fired off the run() twice. With a few modifications that also made the answer more concise, I offer the following;
import com.sun.javafx.application.PlatformImpl;
public class MyFxTest implements Runnable {
public static void main(String[] args) {
MyFxTest main = new MyFxTest();
PlatformImpl.startup((Runnable) main);
}
#Override
public void run() {
// do your testing;
System.out.println("Here 'tis");
System.exit(0); // Optional
}
}
I am having an eclipse View. Inside the view I added a Table. Now I am calling a thread from run method of the view using asyncExec.
My View class is like -
public class SampleViewAction implements IWorkbenchWindowActionDelegate{
Thread t;
int Count;
#Override
public void run(IAction arg0) {
}
}
Now I added a thread like this -
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
#Override
public void run() {
new UDPReadThread();
}
});
Where UDPReadThread is a class extends a thread where in UDPReadThread 's constructor I started the thread.
But I am getting invalid Thread exception.
How to resolve the issue.
Similar to AWT and the EventDispatchThread, SWT must process everything in the UI thread.
Your SampleViewAction is run on the UI thread already, in response to a menu or tool item selection.
It looks like your problem comes from then using an asyncExec(*) which will post the runnable to be run on the UI thread (which delays it), and starting a new thread from that asyncExec Runnable. You may as well simply start your thread, and get rid of that asyncExec.
Your UDPReadThread is not the UI thread. If you need to update UI widgets from UDPReadThread, that's the code that needs the asyncExec:
display.asyncExec(
new Runnable() {
public void run(){
label.setText(text);
}
});
Just as an aside, you should not subclass Thread unless you really are extending threads capabilities. The normal pattern when you just want to start another thread:
UDPReadRunnable udpRunnable = ....;
Thread thread = new Thread(udpRunnable);
thread.start();
You can get more information on the display thread from http://www.eclipse.org/swt/faq.php#uithread