So I have been trying to implement a progress indicator with no luck. I am not sure I understand managing threads with JavaFx very well, despite having read a bit about the Platform.RunLater and Tasks. So here is my use case.
My program allows users to connect to a database and look at some of the schemas and other objects in the database. Sometimes connecting to a large database and pulling up all its tables and info takes a while, so I would like to show a progress indicator. I am not trying to update the progress at all I would just like to make the progress indicator visible at a value of -1 while the process is running to pull everything from the database. Ideally I will have a progress indicator loaded in from an FXML file invisible. When I start the process of pulling info from the database I would like to make it visible.
When trying to make my progress visible it never showed up, so I decide to start out having it visible and making it invisible, just to see what happens. The progress indicator rotated nicely when I opened the program up, but as soon as I try to connect to the database it stopped rotating and just froze. I assume this is what happens when I try to make it visible too which is why it was never showing up.
The following is my current code, I would appreciate any detailed help with explanations so I can understand what is going on. Thanks
from the method that is doing most of the work.
//make progress indicator visible
pi.setVisible(true);
// separate non-FX thread
ExtractorThread t = new ExtractorThread();
t.setCp(cp);
t.start();
//Wait until the thread is done
try{
t.join();
}
catch(Exception e){
e.printStackTrace();
}
//Retrieve the dbextractor from the thread
DbExtractor dbe = t.getDbe();
//move on to the next page in the application
this.caster.goToDataSource(c, cp, dbe);
The ExtractorThread which does the work.
private class ExtractorThread extends Thread{
private ConnectionProperties cp;
private DbExtractor dbe;
public void run() {
dbe = new DbExtractor(cp);
try {
dbe.extract();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public DbExtractor getDbe() {
return dbe;
}
public void setCp(ConnectionProperties cp) {
this.cp = cp;
}
}
If I am supposed to use the Platform.RunLater I am not sure where to use it or why. Any help would be appreciated, thanks!
Use the javafx.concurrent API. Extend Task instead of Thread:
private class ExtractorThread extends Task<DbExtractor>{
private ConnectionProperties cp;
public DbExtractor call() throws Exception {
dbe = new DbExtractor(cp);
dbe.extract();
return dbe;
}
public void setCp(ConnectionProperties cp) {
this.cp = cp;
}
}
Then do:
//make progress indicator visible
pi.setVisible(true);
// separate non-FX thread
final ExtractorThread t = new ExtractorThread();
t.setCp(cp);
t.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
public void handle(WorkerStateEvent event) {
DbExtractor dbExtractor = t.getValue();
this.caster.goToDataSource(c, cp, dbe);
}
});
t.setOnFailed(...); // similarly, to handle exceptions
new Thread(t).start();
I don't code JavaFX, and so I can't give you chapter and verse, but this line:
t.join();
will block the calling code until the background thread is through. Don't do this. Instead use some type of listener to get notified when the background thread finishes. If this were Swing, I'd use a PropertyChangeListener added to a SwingWorker to notify me when the background thread was done. I think that you can still use a PropertyChangeListener to do a similar thing with with JavaFX, but I cannot tell you if this would represent the canonical solution.
Also, don't extend Thread but instead implement Runnable. This won't fix your problem but is basic Java common sense.
Related
I have a JavaFX application that checks for the presence of a database at startup. If the database is not found, a "seed" database must be created before the program can proceed.
Since creating the seed database can be lengthy, I want to display a splash screen and show progress updates as the process proceeds. Only after the seed database is completely written should the program proceed.
Here is the class for the splash screen:
public final class SplashWindow {
private final Label message;
private final ProgressBar progress;
private final Stage splashStage;
public SplashWindow() {
Image img = new Image(IMAGE_PREFIX + "splash_image.png");
double imgWidth = img.getWidth();
ImageView splashImage = new ImageView(img);
splashImage.setFitWidth(imgWidth);
splashImage.setPreserveRatio(true);
message = new Label("Saving seed database...");
message.setPrefWidth(imgWidth);
message.setAlignment(Pos.CENTER);
progress = new ProgressBar();
progress.setPrefWidth(imgWidth);
Pane splashVBox = new VBox(3);
splashVBox.setPadding(new Insets(5));
splashVBox.getChildren().addAll(splashImage, progress, message);
splashStage = new Stage(StageStyle.UTILITY);
splashStage.setScene(new Scene(splashVBox));
}
public void bindMessageProperty(ReadOnlyStringProperty sp) {
message.textProperty().bind(sp);
}
public void bindProgressProperty(ReadOnlyDoubleProperty dp) {
progress.progressProperty().bind(dp);
}
public void show() {
splashStage.show();
}
public void shutdown() {
message.textProperty().unbind();
progress.progressProperty().unbind();
splashStage.hide();
}
}
When run, the splash screen shows correctly with the image, progress bar and text message area.
The SplashWindow class is called by the following method:
private void saveSeedDatabase(ObservableList<RefModel> docList) {
SplashWindow splash = new SplashWindow();
Task<Integer> saveTask = new Task<Integer>() {
#Override
protected Integer call() throws InterruptedException {
updateMessage("Saving references...");
int docsSaved = 0;
for (RefModel rm : docList) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
updateMessage("Saving: " + rm.getTitle());
saveNewReference(rm);
docsSaved++;
updateProgress(docsSaved, docList.size());
}
updateMessage("Saved " + docsSaved + " references to database");
return docsSaved;
}
};
saveTask.setOnSucceeded((WorkerStateEvent t) -> {
splash.shutdown();
});
splash.bindMessageProperty(saveTask.messageProperty());
splash.bindProgressProperty(saveTask.progressProperty());
splash.show();
new Thread(saveTask).start();
try {
saveTask.get();
} catch (InterruptedException | ExecutionException ex) {
// Do nothing
}
}
When run, the splash screen is displayed but never shows the update messages and progress. It shows a wait cursor when the mouse if over the splash screen.
If the try/catch block at the end of the method is commented out, the main program attempts to proceed without waiting for the database to be written. In this case, the splash screen is hidden by the main program window, but does display the update messages as it works. From debugging this, it looks like everything is running on the correct thread -- the database stuff is on a worker thread, the SplashWindow stuff is on the FX event thread.
It seems clear that the call to saveTask.get() is blocking the UI thread, but I am not sure why.
#JewelSea has written a nice alternative that doesn't fit my program architecture very well. It would not be impossible to alter my program to work with his solution.
However, I don't understand why the get() call blocks the UI. What am I doing wrong.
According to the JavaDocs:
(get()) Waits if necessary for the computation to complete, and then retrieves its result.
Retrieving the computed value without blocking the GUI Thread would be done with getValue(). However: This method only returns a result, when the Task has successfully finished its work. That is why you should do this aysnc in the onSucceeded block.
Java is not my mother tongue and I've been fighting with this problem for a little while.
Basically, I am finding a behavioural difference between calling method switchApplets() directly from init(), and calling it from within a new thread spawned by init().
The consequence of calling it from inside the new thread is that the new applet whitescreens -- until/unless the user resizes or minimizes their browser. If called at the end of init(), the new UI renders immediately without any input from the user. But that's not an option because it doesn't wait for the thread to finish its prep work.
Trimmed-down code:
public class PreLoader extends Applet implements AppletStub {
static JProgressBar pBar = null;
static JLabel message;
public void switchApplets() {
try {
Class main_class = Class.forName("MainClass");
Applet main_applet = (Applet)main_class.newInstance();
removeAll();
setSize(0,0);
setLayout(new GridLayout(1,0));
add(main_applet);
main_applet.init();
main_applet.start();
main_applet.setStub(this);
}
catch (Exception e) {
}
}
public void init() {
pBar = new JProgressBar(0, 100);
pBar.setValue(0);
pBar.setStringPainted(true);
message = new JLabel("Beginning work!");
add(message);
add(pBar);
FlowLayout flow = new FlowLayout();
setLayout(flow);
Thread t = new Thread ( new Runnable () {
public void run ()
{
longRunningFunction1();
longRunningFunction2();
message.setText("Work complete! Stand by..");
switchApplets(); //does NOT work as intended from here
return;
}
} );
t.start();
//switchApplets(); //works as intended if called HERE
}
public void longRunningFunction1() {
//perform some tasks, advance progress bar
}
public void longRunningFunction2() {
//perform some tasks, advance progress bar
}
public void start() {
return;
}
public void appletResize(int width, int height) {
return;
}
}
I tried making init() wait for the thread to finish so that I could call switchApplets() from there, but that only blocked the EDT and prevented the UI from updating. Also tried playing with SwingUtilities' invokeLater/invokeAndWait, but even though switchApplets() gets run on the EDT, it seems that it MUST be called directly from init() (or at least the thread init is running on) to have the desired effect.
Why does calling switchApplets() from within a new thread result in a slightly different (and unwanted) UI behaviour?
The consequence of calling it from inside the new thread is that the new applet whitescreens -- until/unless the user resizes or minimizes their browser.
It's likely a deadlock caused by trying to do UI code on the wrong thread.
I tried making init() wait for the thread to finish so that I could call switchApplets() from there, but that only blocked the EDT and prevented the UI from updating.
You're on the right track. You need to call switchApplets() only from the EDT, and only after the work is done on the other thread.
Are you sure you tried using invokeLater() or invokeAndWait() from within the spawned thread after the long running functions were done? It's been a long while since I did applets but I'm not aware of any applet-specific reason why it wouldn't work, and it would work in any other case. I.e.,
public void run()
{
longRunningFunction1();
longRunningFunction2();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
message.setText("Work complete! Stand by..");
switchApplets();
}
});
}
However, the most proper way to do this is with a SwingWorker rather than a manually created thread. SwingWorker (which is not nearly as well-known as it should be) is designed exactly for the goal of performing background tasks on a separate thread while still being able to update the GUI with progress updates and the results. E.g.,
new SwingWorker<Void,Void>() {
#Override
protected Void doInBackground() { // is called on a background thread
longRunningFunction1();
longRunningFunction2();
return null;
}
#Override
protected void done() { // is called on the Swing thread
message.setText("Work complete! Stand by..");
switchApplets();
}
}.execute();
The Void stuff is because SwingWorker is also capable of returning results and sending intermediate progress updates, but this example doesn't use those features.
You indicated that your long running functions are also updating a progress bar. That's another thing that should happen only on the Swing thread. In practice you can often get away without it, but it's dodgy. Your progress updates can use one of the SwingUtilities.invoke methods, or the mechanisms of SwingWorker; either should work. (SwingWorker itself provides two different ways to do it: Call addPropertyChangeListener (Swing thread) and setProgress (background thread), or call publish (background thread) and override process (Swing thread).)
Also, a small suggestion: if it's inconvenient to deal with a checked exception (or impossible to usefully do so), rather than catching and ignoring it, you should at least catch & rethrow it as an unchecked exception:
catch (Exception e) {
throw new RuntimeException(e);
}
That way, the stacktrace and error message of any exception will not be lost.
I am developing a Scoreboard Java application for my work. It uses MySQL to store the score values and a Java application accesses them and displays them on a projector. So far I have managed to create a Java application using Swing. I display all of the scores using jLabels so that they can be updated without completely redrawing the scoreboard.
Now, I need to get the scoreboard to update periodically. I have attempted to use Thread.sleep but I don't know how to interrupt the thread. The reason I need to interrupt the thread is that if the number of entries to display on the scoreboard is changed on the config panel, the scoreboard must redraw in order to display the right nummber.
Currently sleep works fine in the code so long as I don't touch anything. But as soon as I change anything in the ConfigPanel things go awry.
package au.thewebeditor.scoreboard.apps;
import java.lang.*;
public class Program {
private static Scoreboard sb;
private static ConfigPanel cp;
public Program(){
sb = new Scoreboard();
cp = new ConfigPanel();
}
public static void redrawScoreboard() throws NullPointerException{
try{
sb.dispose();
} catch (NullPointerException e){
//DO NOTHING
}
sb = new Scoreboard();
try {
cp.toFront();
} catch (NullPointerException e) {
cp = new ConfigPanel();
}
constUpdates();
}
public static void showConfig(){
cp.setVisible(true);
cp.toFront();
}
public static void main(String[] arguments){
new Program();
constUpdates();
}
private static void constUpdates() {
boolean go = true;
while (go){
try {
Thread.sleep(5000);
Scoreboard.updateScores();
} catch (InterruptedException e) {
//DO nothing
}
}
}
}
When the connfiguration has been changed redrawScoreboard() is called.
At the moment, when redrawScoreboard is called it just sits in the queue while constUpdates keeps counting to 5000. How do I interrupt the sleep so I can redraw the scoreboard. Is sleep even the best option here? Or should I try something else?
you have an issue with Concurency in Swing, any create, update, modify Swing GUI must be done on Event Dispatch Thread, maybe reason for wrapping sb.dispose(); into try - catch by throws NullPointerException
Swing GUI must be created on Initial Thread
there no reason to recreate a new Top-Level Container every 5th. seconds, reuse JComponents added to contianer on app's start_up
use util.Timer to invoke SwingWorker,
Calling constUpdates takes the current thread an puts it in an infinite loop.
If the config UI is calling it it will put the UI thread in an infinite loop.
It'd be better just to have that loop in the main function.
Should you get an InteruptedException you should break out of the loop, not keep going.
You've a heady mix of static and non-static things, try to make it so that objects get passed arround instead.
If you want the config to ask the scoreboard to redraw, pass it the scoreboard so it can call redraw directly and leave the polling alone.
In my Java GUI app I have a JButton and when clicked it calls a function to connect to a database, then calls a function to clear a table in the DB, then calls a function that reads text from one file and loads variables, which calls a function that reads text from another file, compares the data from both and then calls a function to either update or insert data in the DB, all of that works fine.
However my question is related to the JButton, when its clicked I want to run a Indeterminate progress bar just so the user knows work is being done and then right before it leaves the the action listener setIndeterminate to false and set the value of the progress bar to 100(complete), but in my case when you click the button it stays in the clicked state and the progress bar freezes.
What should I implement to prevent this? threading possibly? but Im quite new to threading in java. here is my action listener:
private class buttonListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if( e.getSource() == genButton )
{
progressBar.setIndeterminate(true);
progressBar.setString(null);
try
{
dbConnect(); //connects to DB
clearSchedules(); // deletes data in tables
readFile(); // reads first file and calls the other functions
dbClose();// closes the DB
progressBar.setIndeterminate(false);
progressBar.setValue(100);
}
catch (Exception e1){
System.err.println("Error: " + e1.getMessage());
}
}
}
}
On a side note, I would like to have the action bar actually move as the the program progresses but I wasnt sure how to monitor its progress.
Thanks, Beef.
UPDATE here is my example of SwingWorker and how I used it:
Declared globally
private functionWorker task;
private abstract class functionWorker extends SwingWorker {
public void execute() {
try {
dbConnect();
} catch (SQLException e) {
e.printStackTrace();
}
clearSchedules();
try {
readFile();
} catch (IOException e) {
e.printStackTrace();
}
dbClose();
}
}
Inside my actionPerformed method
if( e.getSource() == genButton )
{
progressBar.setIndeterminate(true);
progressBar.setString(null);
try
{
task.execute();
progressBar.setIndeterminate(false);
progressBar.setValue(100);
}
catch (Exception e1){
System.err.println("Error: " + e1.getMessage());
}
}
The problem is probably related to connecting to doing expensive operations in the UI thread (connecting to a database, reading from a file, calling other functions). Under no circumstances should you call code that uses excessive CPU time from the UI thread, as the entire interface can't proceed while it is executing your code, and it results in a 'dead' looking application, with components remaining in their state at the time before an expensive operation until completion. You should execute another thread, do the expensive work in that, and then use a SwingUtilities.invokeLater(Runnable doRun) with a passed runnable where you'd update the progress.
There may be synchronisation issues relating to the states of components, but you can fix these later.
Could I create the new thread when the action is performed and call the new functions in the thread, or should I do the threading within the actual function itself?
You can start a SwingWorker from your button's handler, as shown here. A related example implementing Runnable is seen here.
One method to handle progressbars are to extend SwingWorker in a class.
SwingWorker takes care of running background tasks for you and so you do not have to implement your own threading that can end up in unknown issues.
To begin with, your class that takes care of progress bar UI should implement PropertyChangeListener
And implement public void propertyChange(PropertyChangeEvent evt) { - to update the progressbar status based on a global variable.
The background task class should look like the following(this could be an inner class) :
class ProgressTask extends SwingWorker<Void, Void> {
#Override
public Void doInBackground() {
//handle your tasks here
//update global variable to indicate task status.
}
#Override
public void done() {
//re-enabled your button
}
}
on your button's event listener :
public void actionPerformed(ActionEvent evt) {
//disable your button
//Create new instance of "ProgressTask"
//make the task listen to progress changes by task.addPropertyChangeListener(this);
//calll task.execute();
}
I have tried to water down code example, you would have to read some tutorial to understand how all these pieces fit together. However, the main point is do not code your Threads, instead use SwingWorker
progressBar.setIndeterminate(true);
progressBar.setValue(0);
dbConnect(); //connects to DB
progressBar.setIndeterminate(true);
progressBar.setValue(10);
clearSchedules(); // deletes data in tables
progressBar.setIndeterminate(true);
progressBar.setValue(50);
readFile(); // reads first file and calls the other functions
progressBar.setIndeterminate(true);
progressBar.setValue(75);
dbClose();// closes the DB
progressBar.setIndeterminate(false);
progressBar.setValue(100);
You will need to tell the progress bar how much progress has been made because it does not know the percentage completed. Better yet, write a method that updates and repaints the progress bar rather than repeating the method calls here.
updateProgressBar(int progress, boolean isDeterminate, String msg){};
You will also need to make sure that your specific button is firing the action performed.
class IvjEventHandler implements java.awt.event.ActionListener {
public void actionPerformed(java.awt.event.ActionEvent e) {
if (e.getSource() == JMyPanel.this.getJButtonUpdate())
connEtoC1(e);
};
};
The connEtoC1(e); should execute a controller class or SwingWorker rather than firing from the GUI
I have some code which takes a few minutes to process, it has to connect to the web for each string in a long array, each string is a url. I want to make it so that everytime it connects, it should refresh the jtextarea so that the user is not staring into a blank page that looks frozen for 20 min. or however long it takes. here is an example of something i tried and didnt work:
try {
ArrayList<String> myLinks = LinkParser.getmyLinksArray(jTextArea1.getText());
for (String s : myLinks) {
jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(jTextArea1, "Parsing Error", "Parsing Error", JOptionPane.ERROR_MESSAGE);
Logger.getLogger(MYView.class.getName()).log(Level.SEVERE, null, ex);
}
The problem is that you need to perform the computation asynchronously. You should create a background thread that performs the computation, and then use SwingUtilities.invokeLater to update the JTextArea.
final ArrayList<String> myLinks = //...
(new Thread()
{
public void run(){
for (String s : myLinks) {
try{
final String result = LinkChecker.checkFileStatus(s) + "\n";
SwingUtilities.invokeLater(new Runnable(){
public void run(){
jtextArea2.append(result);
}
});
}catch(IOException error){
// handle error
}
}
}
}).start();
Edit
It has been pointed out that JTextArea's append function actually is thread safe (unlike most Swing functions). Therefore, for this particular, case it is not necessary to update it via invokeLater. However, you should still do you processing in a background thread so as to allow the GUI to update, so the code is:
final ArrayList<String> myLinks = //...
(new Thread()
{
public void run(){
for (String s : myLinks) {
try{
jtextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}catch(IOException error){
// handle error
}
}
}
}).start();
However, for pretty much any other operation that modifies a Swing object, you will need to use invokeLater (to ensure the modification occurs in the GUI thread), since almost all the Swing functions aren't thread safe.
You need to investigate threading and its relationship to GUI updates in Swing. Anything that affects or makes use of GUI components in Swing must done on a special thread called the Event Dispatch Thread (EDT).
If your code snippet, if it's freezing the GUI, I imagine that it is being run in the EDT. Performing a long-running action on the EDT will make the GUI unresponsive, because no further updates can be done while your long-running process is using the thread.
There is a helper class called SwingWorker that allows you to offload long-running computations to a background thread, and then make updates to the GUI thread when it is complete. The SwingWorker looks after the context switches between the GUI thread and the background thread. You can also display progress bars to let the user know the state of the long-running process, so they know your application hasn't hung.
swing/awt is a single threaded library, so once a component is shown, just changing it's appearance won't work correctly. You need to change the component on the GUI Thread, not from your thread. To do this wrap any code that updates a component with SwingUtilities.invokeLater... as in
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}
});
also you want to limit what you do on the gui thread to avoid the gui from becoming sluggish, so if checkFileStatus is time consuming, execute it outside the run method and store the result in a final local variable, and just access the variable in the run() code.