GUI not updating visually before running ActionEvent - java

To expound a little more, I have a GUI that looks like:
Then I have an action listener on the OK button that starts like:
//OK Button Action Listener
private void okButtonActionPerformed(ActionEvent e) {
//Enable/Disable Buttons
okButton.setEnabled(false);
cancelButton.setEnabled(true);
updateCheckbox.setEnabled(false);
//Move on to a series of other methods here...
Which should, in theory, make this happen:
However, instead, I get the following until ALL methods and other things connected to the OK button are completed:
This obviously can't happen, because the idea is to make the cancel button available and the OK button and several other tick-boxes unavailable for the duration of the program (Image 2), where, instead, it freezes in a half-state (Image 3). Is there any way to combat this?

Every time you execute logic from the GUI you should be using the SwingWorker in the following way:
SwingWorker myWorker= new SwingWorker<String, Void>() {
#Override
protected String doInBackground() throws Exception {
//Execute your logic
return null;
}
};
myWorker.execute();
If you want to update the GUI from inside this logic use InvokeLater:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//To update your GUI
}
});
With this you can be sure that both your logic and your GUI stay responsive.
Edit:
You could also use invokeAndWait if this suits your needs more. Link to related answer

//Move on to a series of other methods here...
Make sure you don't block the GUI thread (EDT) with long-running operations. Instead use SwingWorker.

Are you doing some processing on the same thread that's handling the GUI? You might want to look into a SwingWorker thread to do the heavy stuffin the background so your UI remains responsive if so.

Yup, that's because you have blocked with EDT with your other methods.
You need to use another Thread to do the work in the background otherwise the GUI will be blocked.
In Swing applications it is recommended that any long running tasks are carried out on a SwingWorker.
Take a look at the documentation for an introduction.
A SwingWorker will carry out a task that it is given and it can report back to the GUI when it is done. This will be done in a non-blocking way so that you can still use the GUI while the task it being carried out.
If you want to be able to cancel the background task you need to keep a reference to the SwingWorker so that you can call the cancel method. In this case the work method needs to be interruptable, otherwise the task cannot be cancelled.

Related

Repaint a swing component while a thread is running

I have a thread that I start when I press the start button. What I want to do is repaint the labels so they contain the information that my thread makes changes to. The only problem I am facing is that the jLabels repaint only after the thread is done running. Can someone give me any sugestions on how I can make it repaint while the thread is runnong? Thanks.
Here is a snippet of my code:
Store m = new Store(); //Store extends Thread
private void startActionPerformed(java.awt.event.ActionEvent evt) {
....
//I get the simulation time of the store from a textbox
//the thread runs for this number of seconds
//when it is done, the store is closed(the open variable is set to false)
....
m.start();
while (m.isOpen()) {
queue0.setText(String.valueOf(m.clientiCasai(0)));
//here I will have more queues
....
noOfClients.repaint(); //this is the label that should show the number of clients in queue 0
}
}
The problem is that the actual painting is also done during the EDT's event loop; your while() loop is basically preventing the EDT from proceeding.
One possible workaround would be to have an extra thread that would take care of updating the label.
Your startActionPerformed() method should not run on EventDispatchThread (EDT) which is the Thread that shall be used for all Swing Modification operations. If you block the EDT your UI will not repaint, it will freeze.
Your noOfClients.repaint() call should be done on EDT, but also your call setting the new value to queue0 label should be on EDT.
For simplification. If you your queue0.setText() call on EDT, the repainting will be done for you, so you can remove it.
This can be achieved by calling:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
queue0.setText("<text>");
}
});
To solve your problem you could either pass a reference to the instance holding the method startActionPerformed() to your Store and call it from there when needed or you could start another Thread that monitors the Store progress and propagates it to the Swing EDT.

Make an ActionListener commit a change to a JButton before it has finished executing the entire ActionListener

In Java, I'm using an ActionListener for an array of JButtons. I would like for an earlier part of the ActionListener to set a new ImageIcon to a JButton, that change to be displayed immediately, then near the end of the ActionListener to set the JButton's ImageIcon back to null after a second long delay.
My problem is that none of the changes that happen to the JButton get displayed in the GUI window that it is set in until the ActionListener is completely finished, making the change in the JButton's ImageIcon unnoticeable. Is there any way to make an ActionListener commit a change to a JButton before it has finished executing the entire ActionListener, or should I be going about this differently?
The reason this is happening:
Swing repaints the buttons on the same thread (EDT) as the ActionListener is ran on. Hence if it is executing you ActionListener it cannot repaint since the thread is busy - as simple as that. You may have noticed that while your action listener is executing you also can't properly move your frames around etc. (GUI freezes up).
The solution:
Move heavy processing outside the EDT. It doesn't belong there anyway. As you could have guessed - use a background thread/thread pool for that. A good guide to it is Swing tutorial for concurrency
Notes:
As portrayed in the guide you do not want to modify components outside the EDT. As such the easiest strategy is to make a Runnable to execute on a background thread, start it, change the picture on the button and return without waiting for the task to finish.
public void actionPerformed(ActionEvent ae) {
Runnable task = new Runnable() {..};
executor.execute(task);
button.setIcon(newIcon);
return;
}
Note that this doesn't lock up the EDT for the task, hence allowing Swing to change the picture immediately.
This of course means that the user has no idea if the task has finished or not (And if there were any exceptions)! It is in the background after all! Hence there is an extra state of your execution: GUI is responsive and non-frozen, button is changed, but task is still running. In most applications this may be a problem (the user will spam the button or your background tasks may interleave). In that case you may want to use a SwingWorker to have a "processing" state as well.
public void actionPerformed(ActionEvent ae) {
new TaskWorker().execute();
button.setIcon(loadingIcon); //Shows loading. Maybe on button, maybe somewhere else.
return;
}
private class TaskWorker extends SwingWorker<Void, T> {
public Void doInBackground() {
//Do your task in the background here
}
protected void done() {
try {
get();
button.setIcon(doneIcon);
catch (<relevant exceptions>) {
button.setIcon(failedIcon);
}
}
}
Here done() is called on the EDT when doInBackground() is finished.
You could create a new Thread or a Thread from a Thread pool if you have one. And with that let the task work on a seperate Thread so that the ActionListener returns immediatly. And then in the other Thread you do your code and repaint the button. This is by the way a threory I'm not sure if it will work.

Swing GUI is not updating

I have simple Java Swing application which uses zip4j to encrypt and unpack zip file. It's done by this part of code:
ZipFile zipFile = new ZipFile("dataStorage.zip");
zipFile.setPassword(password);
zipFile.setRunInThread(true);
ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
if (!verify(zipFile)) {
JOptionPane.showMessageDialog(null, "You have entered incorrect password!", "ERROR", 0);
return;
}
zipFile.extractAll("./"); //runs in new thread
//After entering this while GUI freezes
while (progressMonitor.getState() == ProgressMonitor.STATE_BUSY) {
System.out.print("."); // this works normally...
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
//
}
pbEncryptionProgress.setValue(progressMonitor.getPercentDone()); //this is not updating progress bar, but getPercentDone is returning correct data
}
Problem is that progress bar is not being updated. Application GUI seems frozen. However, dots are being printed to console. How can I fix it to update that progress bar?
Please read Concurrency in Swing.
What you are doing is using up all resources of the EDT by sleeping and updating, not leaving any time for it to actually redraw your GUI. The EDT is meant for small operations on the GUI. You should never call Thread.sleep() on the EDT.
What you could do is make a Timer that would run your check every second until the check passes. That way the EDT would be free to not freeze.
A much better way of doing this is by using a SwingWorker. It has methods that do your specific thing:
A task to do in the background (In your case - unzip)
A method to publish a partial result to the GUI (in your case % done)
A method to react to partial results (In your case - update progress)
A method to invoke when done (Not shown in your case, but useful anyway).
Wrap the call pbEncryptionProgress.setValue(progressMonitor.getPercentDone()); in SwingUtilities.invokeAndWait
You shouldn#t do your zipping on the event dispacher thread (which is where all your event-handling takes place). Create a SwingWorker or someething like it to offload your heavy duty on a separate processing thread that can then inform the progress bar that can be updated on the EDT. With your solution all updates to the progress bar can only be processed when the EDT is free again, that is after your zip-operation is finished.

Why doesn't setText work for JLabel component?

I have a JLabel which I want to change momentarily, here is the code I have written to do so:
infoLabel.setText("Added");
try {
TimeUnit.MILLISECONDS.sleep(300);
}
catch(InterruptedException ex)
{
}
infoLabel.setText("Action"); //funny part is when I comment this line it works
My default text for the label is 'Action'
Swing is a single threaded frame work, that means, if you do anything that stops this thread, then it can't respond to any new events, including paint requests.
Basically, TimeUnit.MILLISECONDS.sleep(300) is causing the Event Dispatching Thread to be put to sleep, preventing it from processing any new paint requests (amongst other things).
Instead, you should use a javax.swing.Timer
Take a look at
Concurrency in Swing
How to use Swing Timers
For more details
For example...
infoLabel.setText("Added");
Timer timer = new Timer(300, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
infoLabel.setText("Action");
}
});
timer.setRepeats(false);
timer.start();
Note, 300 milliseconds is a really short time, you might like to start with a value a little larger like 2000, which is 2 seconds ;)
You're sleeping the Swing event thread putting the entire GUI to sleep. Don't do that. Use a Swing Timer instead.
Your application is run on a single thread, so when you sleep the thread, you prevent it from making any GUI updates.
Are you sure you are doing things properly? By doing everything (including sleep) in the GUI thread, it will always be busy and never get back to Java in order to let the GUI be redrawn.
Search for EDT (Event dispatch thread) for more info. Here is one question on the subject: Processing code doesn't work (Threads, draw(), noLoop(), and loop())

How to make button click block in Java Swing?

I am very new to Swing.
I have
itemActionButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg) {
itemAction();
}
});
But when the button is clicked, instead of running this action on another thread, I would like the parent's form's thread to wait until it the action is completed before refreshing, allowing additional clicks, etc.
How can I do this?
Code in the ActionListener executes on the EDT, which prevents the GUI from repainting and responding to other events.
If you have a long running task and you don't want to block the EDT then you need to use another Thread.
Read the section from the Swing tutorial on Concurrency for more information and a solution by using a SwingWorker.

Categories

Resources