Reputation: 36289
I have a task that can take a few seconds to a few minutes, and when I click on the button to execute the task, it runs the task but does not always disable button A
and enable button B
.
Here is the code that I am using:
@FXML
public void onExecute(ActionEvent event){
btnExecute.setDisable(true);
btnStopExec.setDisable(false);
new Thread(){
@Override
public void run(){
Platform.runLater(() -> {
QueryTable qt = new QueryTable(currentMysqlConn, currentDatabase);
qt.setTabPane(resultsTabPane);
qt.setQuery(queries);
qt.executeQueries();
btnExecute.setDisable(false);
btnStopExec.setDisable(true);
});
}
}.start();
}
If I comment out the button disabling in Platform.runLater()
button A
gets disabled and button B
get enabled, but after Platform.runLater()
runs. Why does this work sometimes and not others?
Upvotes: 2
Views: 5177
Reputation: 209225
According to the Javadocs for Platform.runLater(...)
, it
Runs the specified Runnable on the FX Application Thread
So the only thing your background thread does is to schedule all your time-consuming database work to run on the FX Application Thread: your background thread is basically redundant, and your UI will be unresponsive while the database work is running.
If it happens that a frame is rendered between the calls to btnExecute.setDisable(true);
and the runnable you define getting executed, then you will see the disabled state change. If not, then all your code gets executed during the same frame rendering(*), so you will never see the disabled state change.
Platform.runLater()
should be called from a background thread just to update the UI. So you could make this work as follows:
@FXML
public void onExecute(){
btnExecute.setDisable(true);
btnStopExec.setDisable(false);
new Thread(){
@Override
public void run(){
QueryTable qt = new QueryTable(currentMysqlConn, currentDatabase);
qt.setTabPane(resultsTabPane);
qt.setQuery(queries);
qt.executeQueries();
Platform.runLater(() -> {
btnExecute.setDisable(false);
btnStopExec.setDisable(true);
});
}
}.start();
}
Platform.runLater(...)
is a pretty low-level approach for FX work. The javafx.concurrent
package defines a higher-level API for this: in particular the Task
class encapsulates a background task and provides callbacks that will be executed on the FX Application Thread so you can update the UI. The Javadocs have copious examples, but you could do:
@FXML
public void onExecute(){
btnExecute.setDisable(true);
btnStopExec.setDisable(false);
Task<Void> databaseTask = new Task<Void>() {
@Override
public void call(){
QueryTable qt = new QueryTable(currentMysqlConn, currentDatabase);
qt.setTabPane(resultsTabPane);
qt.setQuery(queries);
qt.executeQueries();
return null ;
}
};
databaseTask.setOnSucceeded( event -> {
btnExecute.setDisable(false);
btnStopExec.setDisable(true);
});
new Thread(databaseTask).start();
}
(*) This is a somewhat imprecise statement, but it's qualitatively correct. Technically, the rendering thread blocks while actions are being executed on the FX Application Thread. (The two are not the same thread, but they have a large amount of synchronization between them.) Thus it's impossible for a frame to be rendered between the call to QueryTable qt = new QueryTable(...);
and btnStopExec.setDisable(true);
. It is possible for a frame to be rendered between btnStopExec.setDisable(false);
and the execution of your runnable (i.e before QueryTable qt = new QueryTable(...);
). If such a frame is rendered, you see the disabled state change; if not, you don't. Whether or not that happens is just down to the timing of the calls with respect to the "pulses" (frame renderings), which are targeted to happen every 1/60th second.
Upvotes: 4