I have a Window Listener on the main JFrame of my application. I also have a button listener on a button within the application. I used this as the pattern for the button listener:
good.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
// We're going to do something that takes a long time, so we
// spin off a thread and update the display when we're done.
Thread worker = new Thread() {
public void run() {
// Report the result using invokeLater().
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for(int i=0; i<1000000; i++){
System.out.println("foo");
}
}
});
}
};
worker.start(); // So we don't hold up the dispatch thread.
}
});
When I click on the button, I see the printout for each iteration, but the window listener is not triggered until after the loop has finished executing. The print 'foo' is just there to simulate something that takes a while, and I want the listener to be triggered as soon as the window event occurs (which could be somewhere in the middle of the run() method execution), but it seems like it's not being triggered until the end of the for loop.
Any idea why?
Swing is single-threaded - the Event Dispatch Thread (EDT) manages painting and events for Swing. Any long running tasks placed on the EDT will prevent the EDT from doings tasks until the long running task is complete (in other words, the UI will seem like it has locked up). Here, your code dispatches the long running process (the for loop) onto the EDT using SwingUtilities - if you have a long running task and want a responsive Swing UI during the process, then place your long running task into it's own Thread, or use a SwingWorker.
So, all this...
// Report the result using invokeLater().
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for(int i=0; i<1000000; i++){
System.out.println("foo");
}
}
});
Will do is, is cause the loop to be executed within the context of the Event Dispatching Thread, which will prevent the EDT from processing the event queue, including paint request.
In your case, it might be easier to use a SwingWorker which you can use to publish/process information from the background thread to the EDT as well as supports progress notification
See How to use SwingWorker for more details
Related
I originally was attempting to update a JFrame and JPanel several times while in a Java Action Listener, but both would only update when the Action Listener completed all its tasks. Here is the link to my original question (Refreshing a JFrame while in an Action Listener).
I was told in the feedback to that question that Swing Worker should solve my problems. However, when I implemented Swing Worker (as seen below), nothing changed. The JFrame and JPanel still updated only when the Action Listener completed all tasks. My question is, am I missing something below? If not, how can I implement this in an Action Listener to properly update the Frame and Panel timely?
#Override
protected Integer doInBackground() throws Exception{
//Downloads and unzips the first video.
if(cameraBoolean==true)
panel.add(this.downloadRecording(camera, recording));
else
panel.add(new JLabel("Could not contact camera "+camera.getName()));
panel.repaint();
jframe.repaint();
return 1;
}
private JLabel downloadRecording(Camera camera, Recording recording){
//does a bunch of calculations and returns a jLabel, and works correctly
}
protected void done(){
try{
Date currentTime = new Timestamp(Calendar.getInstance().getTime().getTime());
JOptionPane.showMessageDialog(jframe, "Camera "+camera.getName()+" finished downloading at "+currentTime.getTime());
}catch (Exception e){
e.printStackTrace();
}
}
You have a misundertanding about how SwingWorker works. This class is intended to provide a way to update the GUI while heavy tasks are being performed. All of this is because Swing components updates take place in the Event Dispatch Thread (a.k.a. EDT) which is a particular thread.
For instance, if you click a button and perform a time consuming task all in the EDT, then this thread will block untill this task finishes. Consequently, you'll see your GUI is frozen.
Keeping this in mind, doInBackground() method runs in another different thread that's not the EDT which is ok. So don't call any Swing method in there:
protected Integer doInBackground() throws Exception{
//Downloads and unzips the first video.
if(cameraBoolean==true) // just use if(cameraBoolean), since this is a boolean
panel.add(this.downloadRecording(camera, recording)); // NO!
else
panel.add(new JLabel("Could not contact camera "+camera.getName())); //NO!
panel.repaint(); //NO, never!
jframe.repaint();//NO, never!
return 1;
}
Add a JLabel to this panel before executing your SwingWorker and update its text using publish() and process() methods instead:
JPanel panel = new JPanel();
final JLabel progressLabel = new JLabel("Some text before executing SwingWorker");
panel.add(progressLabel);
SwingWorker<Integer, String> worker = new SwingWorker<Integer, String>() {
#Override
protected Integer doInBackground() throws Exception {
if(cameraBoolean){
pubish("Starting long process...");
//Some processing here
publish("Intermediate result to be published #1");
//Some other processing stuff
publish("Intermediate result to be published #2");
//And so on...
return 0;
} else {
publish("Could not contact camera "+camera.getName());
return -1;
}
}
#Override
protected void process(List<String> chunks) {
for(String string : chunks){
progressLabel.setText(string);
}
}
#Override
protected void done() {
progressLabel.setText("Finished!!!");
}
};
worker.execute();
Both process() and done() methods take place in the EDT so it's safe make GUI updates there. Take a look to this excelent example: Swing Worker Example for more details.
Maybe because you repaint your panel/frame just when the synchronous call this.downloadRecording(camera, recording) is finished?
Try to only put this call into the doInBackground() method, because (so I guess) that's the one that takes a long time and for all this time the JFrame gets not refreshed.
You can't update UI in next way:
panel.repaint();
jframe.repaint();
In your doInBackground method you must to call publish(V... chunks) method, that Sends data chunks to the process(java.util.List<V>) method.(according docs) and than in method process(List<V> chunks) you can update your UI(according docs process method - Receives data chunks from the publish method asynchronously on the Event Dispatch Thread.). SwingWorker docs.
So, override process method for updating, and call publish method.
Also you can use Executors for background processes. In this case your UI will be working in EDT and your background process in another thread. Example:
Executors.newSingleThreadExecutor().execute(new Runnable() {
#Override
public void run() {
// run background process
}
});
EDIT: good example of SwingWorker
this is how I'm trying to accomplish this:
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Hello there");
Thread.sleep(1000);
panel.updateUI();
}
});
I set Enter button as the default button so when I keep pressing it the button press maybe 100 times or more but because I'm using Thread.sleep(1000) it takes some time so I have time to type in my JtextField or even close the window but can't do anything.
also, I tried to put btnNewButton.addActionListener() in the run method of a thread but no difference.
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Hello there");
Thread.sleep(1000);
panel.updateUI();
}
});
}
});
thread.start();
// I used try catch block in real code
can anyone help me to solve the issue?
**I'm creating this application in eclipse using windowsBuilder.
This is because of something that is called EDT - the Event Dispatch Thread.
This special purpose thread is responsible for all the events in your GUI - including drawing (refreshing) and interactions (button click). This means, that exactly the same thread is used to draw your application as the one that is used to execute actionPerformed code. Since you are literally halting that thread for some amount of time, your application will be non responsive for that exact period of time.
Whatever is being executed on user interactions should be short and execute fast for the reason of not blocking the application. If you need to do some heavy stuff, there is a facility for that purpose called SwingWorker that allows you to easily do some processing in the background thread (pool) and schedule UI updates during execution (like update of progress bar)
In your second "thread" snippet, your thread is not doing anything beside adding actionListener to the button, and then it terminates (probably after less than 1ms ;)) - action callback is still executed by the EDT. If you would start that custom thread from inside of run method - then it would be indeed in parallel and GUI would not be frozen - but again, SwingWorker is the way to go here.
Not entirely sure what your code is supposed to do, but if you want to press the button and then 1000ms later it will check your field you should do something like this:
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
System.out.println("before 1000ms");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after 1000ms");
System.out.println("Reading the text field ...");
}
});
thread.start();
System.out.println("after the thread");
Output:
after the thread
before 1000ms
after 1000ms
Reading the text field ...
I have a problem while creating a JProgressBar which is set to indeterminate.
The following code is my implementation of the JProgressBar and is called/constructed from another class:
public class Progress implements Runnable
{
private JFrame frameProgress;
private JProgressBar progressBar;
public Progress(String title, String message)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e)
{
GlobalVariables.LOGGING_logger.error("Error instatiating progress bar.",
e);
}
UIManager.put("ProgressBar.selectionForeground", Color.black);
UIManager.put("ProgressBar.selectionBackground", Color.black);
this.frameProgress = new JFrame(title);
this.frameProgress.setIconImage(GlobalVariables.GUI_icon.getImage());
this.frameProgress.setSize(300, 60);
this.frameProgress.setLocation(16, 16);
this.progressBar = new JProgressBar();
this.progressBar.setStringPainted(true);
this.progressBar.setString(message);
this.progressBar.setIndeterminate(true);
this.frameProgress.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frameProgress.add(this.progressBar);
this.frameProgress.setResizable(false);
this.frameProgress.setVisible(true);
}
public void start()
{
new Thread(this).start();
}
public void close()
{
this.frameProgress.dispose();
this.frameProgress = null;
this.progressBar = null;
}
#Override
public void run()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
}
}
The caller of this JProgressBar is the following code snippet:
Progress p = new Progress("bla", "blub");
p.start();
boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this);
p.close();
And now i want, while the xmlWriter.writeCommonSettingsFromGUI(this); is doing something, that the JProgressBar is shown to the user and is working while the algorithm is running.
How can I achieve this? I don't know so much about threading and searched in many other forums, but I don't found any answer for my question.
Please help me and thank you in advance ;)
EDIT:
The Progress JFrame opens up with no content for that time, the algorithm is running.
You are probably facing concurrency issues with Swing. Assuming that the following code runs on the EDT (Event Dispatching Thread):
Progress p = new Progress("bla", "blub");
eventually, this will open a JFrame with a progress bar in it.
I would consider using a JDialog instead of a JFrame
I would not force the size of the JFrame, but rather call pack()
Then, still running on the EDT (and thus blocking all UI-events such as repaint, mouse clicks, etc...), you call p.start() which starts a new Thread() which will invoke run() which itself calls
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
This basically won't do anything except push an additional event on the EventQueue and it will run after all currently pending events. This event will run... "nothing" since your Runnable is just empty. The new Thread dies almost immediately. So all this code is useless.
Still pursuing on the EDT, you call boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this); (btw, "successful" ends with only one 'l'). This will continue on blocking the EDT, preventing repaints from occurring and preventing the JProgressBar from painting itself. Eventually you will dispose the JFrame but since all this code is running on the EDT, the user will not see much of the progress bar and the UI will look frozen.
Consider reading the Swing tag wiki (especially the very last part with 3 important links).
Using a SwingWorker should help you out in this.
I am making a game having squares in it (a grid of panels) and when the game ends there is an algorithm that changes the color of the panels one by one in a "live" fashion where the user watches the squares change color slowly. I try something like:
Thread.sleep(1000);
grid.getComponent(boxNumber).setBackground(Color.YELLOW);
Thread.sleep(1000);
grid.getComponent(boxNumber).setBackground(Color.ORANGE);
Although the color of a box changes to Yellow, it does not change to Orange afterwards. Anyone have any ideas? Hope I was able to be clear.
Read the section from the Swing tutorial on Concurrency to understand why you should not be using the sleep() method.
One solution is to use a SwingWorker, then you can 'publish' the color of the component so it can be updated properly on the EDT and you can invoke the sleep() method in the worker as well.
These need to happen on the Swing event thread. call set background via:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
grid.getComponent(boxNumber).setBackground(Color.ORANGE);
}
});
Note, your Thread.sleep() should not be in the event thread (or directly from within a Swing event listener (ActionListener, WindowListener, etc).
It would also be prudent to look the Swing Timing Framework which is specifically for things like this.
-Generally its not a good idea to do Thread.sleep(1000); in the EDT. You should try using Timers.
-You also need to call revalidate()/validate() and repaint() afterward.
So maybe something like this:
Timer yellowTimer = new Timer(1000,new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jtp.setBackground(Color.YELLOW);
//call revalidate()/validate() and repaint() afterward
jtp.revalidate();
jtp.repaint();
}
});
yellowTimer.setRepeats(false);
Timer orangeTimer = new Timer(2000,new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jtp.setBackground(Color.ORANGE);
//call revalidate()/validate() and repaint() afterward
jtp.revalidate();
jtp.repaint();
}
});
orangeTimer.setRepeats(false);
yellowTimer.start();
orangeTimer.start();
I have a SwingWorker thread that launches a modal dialog box (from a property change listener that listens to the StateValue of started) and the swing worker proceeds to do its work. However, it looks like the done method is not called because that is called on the EDT but the swing worker's modal dialog is blocking the EDT. So, I can't close the dialog from the EDT (or from the done method). Right now I'm just closing the dialog from the doInBackground at the end of that method, but that seems a little unsafe from the doInBackground since it's not on the EDT. What's the best way to handle this? thanks.
The dispatch loop should continue to dispatch the events associated with SwingWorker even when a modal dialog is displayed.
This works for me.
import javax.swing.*;
public class Unions {
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() { public void run() {
runEDT();
}});
}
private static void runEDT() {
final JDialog dialog = new JDialog((JFrame)null, true);
new SwingWorker<Void,Void>() {
#Override protected Void doInBackground() throws Exception {
// But this is working.
Thread.sleep(3000);
return null;
}
#Override protected void done() {
dialog.setVisible(false);
}
}.execute();
dialog.setVisible(true);
}
}
For reference:
When a modal dialog is launched in Swing, the execution of that thread is stopped until the dialog is closed.
This is why your done() method was never called (doInBackground() couldn't finish and done() is only called after that).
Opening a modal-dialog from an action called by the EDT thread is slightly different. The EDT itself will continue to process events but the actual event thread code (the code of the action) which opens the modal dialog still gets blocked (and waits until the dialog is closed).
Naturally, in case of non-modal dialogs, this problem never surfaces.
By the way: You should never open a dialog from outside the EDT.
If the decision is made on a non-EDT thread, you need to use SwingUtilities.invokeLater() to actually open the dialog.
Sounds complicated but it is actually not, once you master the concept of the EDT.