I have a problem getting the JavaFX UI to keep active while performing a background Task.
I have set up this very simple code -
#FXML
ProgressBar prgbProgress;
#FXML
private void onClick(ActionEvent event) {
Task <Void> t = new Task <Void> () {
#Override
protected Void call() throws Exception {
for (int i = 0; i < 10; i++) {
updateProgress(i, 9);
Thread.sleep(1000);
}
return null;
}
};
prgbProgress.progressProperty().bind(t.progressProperty());
new Thread(t).run();
}
What I expect to happen is to have the progress bar update every ~1 second until the task is complete. Instead, the UI completely freezes for 10 seconds, after which the progress bar appears completed.
Just to make it clear - the problem isn't only that all of the updates appear at once in the end, but also that the UI is completely unresponsive until then.
I have read just about any other question about this topic, but can't find an answer. What am I doing wrong?
Thanks.
Use start() instead of run()
#FXML
ProgressBar prgbProgress;
#FXML
private void onClick(ActionEvent event) {
Task <Void> t = new Task <Void> () {
#Override
protected Void call() throws Exception {
for (int i = 0; i < 10; i++) {
updateProgress(i, 9);
Thread.sleep(1000);
}
return null;
}
};
prgbProgress.progressProperty().bind(t.progressProperty());
//new Thread(t).run(); // wrong
new Thread(t).start(); // right
}
Related
I have a javafx application. I programmed it such that the application starts with a progress bar that runs to completion. On completion, The window closes and a login window opens. After you sign in, the login window closes and the main window opens.
But that is not what is happening in my application.
I made a task that runs the progressbar from 0 - 100 and I made the task to run on a Thread. When the task is completed, the window closes and login window opens.
The problem I am encountering is that when I sign in using the login window, the main window opens but the login window didn't close.
I wrote a method to close the login window when the main window opens, but That didnt work.
Please what am I missing?
This is the task class
public class TaskClass implements Runnable {
#Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
progress.setProgress(i / 100.0);
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(RunningController.class.getName()).log(Level.SEVERE, null, ex);
}
}
Platform.runLater(new Runnable() {
#Override
public void run() {
// this is the method that closes the window on which the progress bar is running
closed(clos);
// this is the method that opens the login
windowloadwindow("/inventory/management/login/InventoryManagementlogin.fxml", "DeMak Inventory");
}
});
}
}
This loads the progress bar on a thread
Thread thread;
#FXML
private JFXProgressBar progress;
#Override
public void initialize(URL url, ResourceBundle rb) {
progress.setProgress(0.0);
Runnable tasks = new TaskClass();
thread = new Thread(tasks);
thread.start();
}
After you must have signed in using the login window, This is meant to happen
loadwindow("/inventory/management/ui/Main/main.fxml", "Deemax Inventory"); // a method that opens the main window
closed(closew); // a method that closes the login window
In order to synchronize Platform.runLater() code and background thread code you could use the CountDownLatch Class as following:
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
CountDownLatch sync = new CountDownLatch(1);
Platform.runLater(()->{
// GUI code
// ...
// end of GUI code
sync.countDown();
});
sync.await(); // This method will wait for sync.countDown() to be executed
// ...
// code here will be executed after GUI code has finished
// ...
return null;
}
};
Thread thread = new Thread(task);
thread.start();
But, instead you can solve your problem much easier like this:
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
for (int i = 0; i < 100; i++) {
final int finalCounter = i;
Platform.runLater(()->{
try {
progress.setProgress(finalCounter / 100.0);
} catch (InterruptedException e) {
Logger.getLogger(RunningController.class.getName()).log(Level.SEVERE, null, ex);
}
});
Thread.sleep(100);
}
return null;
}
#Override
protected void succeeded() {
// code here will be executed after the thread had been completed
Platform.runLater(()->{
new Alert(Alert.AlertType.INFORMATION, "Thread Succeeded!").show();
});
closed(clos); // this is the method that closes the window on which the progress bar is running
loadwindow("/inventory/management/login/InventoryManagementlogin.fxml", "DeMak Inventory"); // this is the method that opens the login window
super.succeeded();
}
#Override
protected void failed() {
Platform.runLater(()->{
new Alert(Alert.AlertType.ERROR, "Thread Failed!").show();
});
super.failed();
}
};
Thread thread = new Thread(task);
thread.start();
This is my closed method
private void closed(Node nor) {
Stage stage = (Stage)nor.getScene().getWindow();
stage.close();
}
This is my node object.... It is a vbox containing an X image
#FXML
private VBox clos;
so I pass the node to the method like this.
closed(clos);
The application reacts on actions which occur on gamepad. When button is pressed something happens on UI. But I ran at the issue with app hangs up or "java.lang.IllegalStateException: Not on FX application thread" exception.
In order to fix it I tried the following approaches: Platform.runLater() and Task usage. But it didn't help.
Here is the problem code:
public class GamepadUI extends Application{
private static final int WIDTH = 300;
private static final int HEIGHT = 213;
private Pane root = new Pane();
private ImageView iv1 = new ImageView();
private boolean isXPressed = false;
#Override
public void start(Stage stage) throws Exception {
initGUI(root);
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public void pressBtn() {
if(!isXPressed) {
iv1.setVisible(true);
isXPressed = true;
}
}
public void releaseBtn() {
if(isXPressed) {
iv1.setVisible(false);
isXPressed = false;
}
}
private void initGUI(final Pane root) {
Image image = new Image(Props.BUTTON);
iv1.setImage(image);
iv1.setLayoutX(198);
iv1.setLayoutY(48);
iv1.setVisible(false);
root.getChildren().add(iv1);
runTask();
}
public void runTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
};
new Thread(task).start();
}
public static void main(String[] args) {
launch(args);
}
public void initStubGamepad() {
Random rnd = new Random();
try {
while (true) {
if (rnd.nextInt(30) == 3) {
pressBtn();
} else if (rnd.nextInt(30) == 7) {
releaseBtn();
}
}
} catch (Exception ex) {
System.out.println("Exception: " + ex);
}
}
}
initStubGamepad() emulates gamepad buttons activity polling. When user presses any button (rnd.nextInt(30) == 3) - an image appears on the UI. When user releases that button (rnd.nextInt(30) == 7) - an image disappears from the UI.
In case above java.lang.IllegalStateException: Not on FX application thread occurs. If you change runTask() to something like this:
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
Then app will hang or even main UI won't appear at all, but gamepad activity continues.
What I want is just to show/hide different images when some activity is detected on gamepad (btw, there's no way to monitor gamepad activity except for gamepad polling in an infinite loop). What did I wrong
Explanation
In the first scenario, when you are using
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
}
Inside initStubGamepad(), which is running on a Task, you are trying to update the UI components inside pressBtn() and releaseBtn() methods, which is why you are facing a
java.lang.IllegalStateException: Not on FX application thread
because all the UI updates must occur on the JavaFX thread
In the second scenario, when you are using
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
the UI doesnt appear, because you have an infinite loop inside the initStubGamepad(), which puts the JavaFX application thread run on an infinite loop
Solution
By the time you have reach here, you must have already found the solution. In case you haven't, try try to put the update the Javafx components on the UI thread. So, instead of calling initStubGamepad() inside Platform.runLater, try calling pressBtn() and releaseBtn() inside it.
Try using
while (true) {
if (rnd.nextInt(30) == 3) {
Platform.runLater(() -> pressBtn());
} else if (rnd.nextInt(30) == 7) {
Platform.runLater(() -> releaseBtn());
}
}
or you may also use
public void pressBtn() {
if(!isXPressed) {
Platform.runLater(() -> iv1.setVisible(true));
isXPressed = true;
}
}
So I have a thread running and inside that thread, it updates my textArea and it should update my progressBar. I'd like to be able to update the progressBar by calling progressBar.setProgress(progress/totalProgress); without having to bind a task to it. Is this even possible?
Some sample/pseudocode because I can't actually upload the code that's being run...
private int progress;
private ProgressBar progressBar;
private int totalProgress;
public void init() {
progress = 0;
totalProgress = 10; // some max progress number
progressBar.setProgress(progress/totalProgress);
new Thread(new Runnable() {
public void run() {
try {
startThread();
} catch (Exception ex) {
System.out.println(ex);
}
}
}).start();
}
public void startThread() {
for(int i = 0; i < someSize; i++) {
textArea.append("some new message);
progress++;
progressBar.setProgress(progress/totalProgress);
}
}
When I print progress and progressBar.getProgress inside the for loop, I can see it incrementing and actually "updating" but I can't get the progressBar UI to update.
Similar to how Swing has a JProgressBar and you can update it via progressBar.setValue(counter); inside a thread that's already doing other things, I'd like to be able to update the ProgressBar for FX inside a thread that's also running other things.
I have a method like below.
ProgressWindow is a sub class of JFrame containing JProgressBar.
addProgress() increments a value in the JProgressBar.
If I call this method from a method in another class, a frame of ProgressWindow will show up but not JProgressBar and some JLabels inside the frame. They show up after the last line (System.out.println("finish")).
If I call this method in a main method in the class containing this method, then every component (Bar, labels...) instantly shows up.
What can I do for showing the window correctly?
static void search(){
ProgressWindow window = new ProgressWindow();
window.setVisible(true);
ExecutorService execs = Executors.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
Collection<Callable<Void>> processes = new LinkedList<>();
for (int i = 0; i < 100; i++) {
processes.add(new Callable<Void>() {
#Override
public Void call() throws Exception {
progressWindow.addProgress(); // increment progress value
return null;
}
});
}
try {
execs.invokeAll(processes);
} catch (Exception e) {
e.printStackTrace();
} finally {
execs.shutdown();
}
System.out.println("finish");
The main problem is you seem to be calling search from the context of the Event Dispatching Thread.
The problem occurs because you are using execs.invokeAll which blocks until all the callables have finished running.
This means that the EDT is unable to process new events in Event Queue, including repaint events, this is why your UI is coming to a stand still...
There are a number of issues you are now going to face...
You should never update/modify a UI component from any thread other than the EDT
You should block the EDT for any reason
You seem to want to know when the search is complete, so you know need some kind of event notification...
The first thing we need is some way to be notified that the search has completed, this means you can no longer rely on search returning when the search is complete...
public interface SearchListener {
public void searchCompleted();
}
Next we need an intermeditate search method that builds the UI and ensure that the search is launched within it's own Thread...
static void search(final SearchListener listener) {
final ProgressWindow window = new ProgressWindow();
window.setVisible(true);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
search(listener, window);
}
});
t.start();
}
Then we need to modify the original search method to utilise the SearchListener interface to provide notification when the search is complete...
static void search(final SearchListener listener, final ProgressWindow window){
ExecutorService execs = Executors.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
Collection<Callable<Void>> processes = new LinkedList<>();
for (int i = 0; i < 100; i++) {
processes.add(new Callable<Void>() {
#Override
public Void call() throws Exception {
// This method needs to ensure that
// what ever it does to the UI, it is done from within
// the context of the EDT!!
progressWindow.addProgress();
return null;
}
});
}
try {
execs.invokeAll(processes);
} catch (Exception e) {
e.printStackTrace();
} finally {
execs.shutdown();
}
System.out.println("finish");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
listener.searchCompleted();
}
});
}
Now, without the source code for addProgress, I might be tempted to use
processes.add(new Callable<Void>() {
#Override
public Void call() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
progressWindow.addProgress();
}
});
return null;
}
});
}
Instead...
Take a look at Concurrency in Swing for more details
Sounds like you what you're wanting to do is invoke the setVisible on the Swing UI thread, you can do this with invokeAndWait or invokeLater.
So something like:
final ProgressWindow window = new ProgressWindow();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
window.setVisible(true);
}
});
I have some simple code with a for loop. In each pass of the loop I must increment the JProgressBar; however, this isn't working. See below:
public void atualizarBarraDeProgresso(final int valorGeral, final int valorAtual) {
Thread threadProgressoCarregamento = new Thread() {
#Override
public void run() {
jProgressBarPersistindo.setValue(valorAtual);
}
};
threadProgressoCarregamento.start();
}
I'm calling the method "atualizarBarraDeProgresso" in a loop like below:
progressBar.setMinimum(0);
progressBar.setMaximum(qtd);
for(int i = 0; i < qtd; i++) {
atualizarBarraDeProgresso(qtd, i + 1);
doSomething();
}
But nothing happens with my progressBar.
try adding a thread before a for statment. I hope it works
progressBar.setMinimum(0);
progressBar.setMaximum(qtd);
new Thread(){
#Override
public void run() {
for(int i = 0; i < qtd; i++) {
atualizarBarraDeProgresso(qtd, i + 1);
doSomething();
}
}
}.start();
This should be easily managed with a SwingWorker implementation. SwingWorkers are useful when you need to "do something" but don't want to block the GUI while doing it. The class also gives you a useful API for communicating back to the EDT when you need to update a GUI component while doing other work via the publish()/process() methods.
The below implementation handles your loop on a worker thread so that it does not block the EDT (the GUI thread). Calls to publish(Integer...) are relayed to the EDT as a call to process(List) which is where you want to update your JProgressBar, because like all Swing components you should only update a JProgressBar on the EDT.
public class MySwingWorker extends SwingWorker<Void, Integer> {
private final int qtd;
private final JProgressBar progressBar;
public MySwingWorker(JProgressBar progressBar, int qtd){
this.qtd = qtd;
this.progressBar = progressBar;
}
/* This method is called off the EDT so it doesn't block the GUI. */
#Override
protected Void doInBackground() throws Exception {
for(int i = 0; i < qtd; i++) {
/* This sends the arguments to the process(List) method
* so they can be handled on the EDT. */
publish(i + 1);
/* Do your stuff. */
doSomething();
}
return null;
}
/* This method is called on the EDT in response to a
* call to publish(Integer...) */
#Override
protected void process(List<Integer> chunks) {
progressBar.setValue(chunks.get(chunks.size() - 1));
}
}
You can start it like this
int qtd = ...;
progressBar.setMinimum(0);
progressBar.setMaximum(qtd);
SwingWorker<? ,?> worker = new MySwingWorker(progressBar, qtd);
worker.execute();