I have a JRuby script that opens a Java dialog to report on the progress of the script.
I am capturing a windowclosing event, and want the dialog to wait until some cleanup in the JRuby script has occurred, and then dispose. Instead, the dialog just hangs when the user presses the top right red x button.
How do I correctly call the wait method to wait on that flag change? Am I using the lock object correctly?
A jruby script calls this dialog.
If a user presses the top right red X, the dialog captures the windowclosing event and sets a 'cancelled' flag.
The script keeps an eye on that flag, then starts shutting down some long running tasks and what not . When done, it updates a flag on the dialog to say cleanup has occurred.
Meanwhile, the dialog is looping, waiting on that flag to change. Then it calls dispose().
I've tried using sleep. For some reason, that upsets something between my JRuby and dialog, cleanup occurs okay, but the dialog does not dispose.
Using wait, with synchronize(this) generates a IllegalMonitorException, but the script cleans up and the dialog does dispose correctly apart from the exception.
Have looked at a bunch of other posts on how to synchorize the wait method, would very much like to understand this.
Thanks very much for any assistance.
Dialog class as follows:
import javax.swing.*;
import java.awt.*;
public class MyDialog extends JDialog {
private boolean userCancelled;
private boolean scriptCleanedUp;
//private static Object lock = new Object();
public MyDialog(lock) {
userCancelled = false;
scriptCleanedUp = false;
setDefaultCloseOperation(2);
//[..] add various controls to dialog
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
// the jruby script keeps an eye on this flag to see if the user has cancelled the dialog
userCancelled = true;
/* once cancelled, wait for script to flag that it has performed its cleanup
*/
/* here is the problem area, what do I need to synchronize to use the wait method?
*/
while (!scriptCleanedUp) {
try {
synchronized (lock) {
lock.wait(1000000000);
}
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
dispose();
}
});
super.paint(super.getGraphics());
}
public boolean user_cancelled() { return userCancelled; }
public void setScriptCleanedUpToTrue() { this.scriptCleanedUp = true; }
public static void forBlock(MyDialogBlockInterface block)
{
MyDialog dialog = new MyDialog(new Object());
dialog.setVisible(true);
block.DoWork(dialog);
dialog.dispose();
}
}
And if it helps, this is how the JRuby script calls the dialog
MyDialog.forBlock do |dialog|
#do long running jruby task here
end
You've got a lot of problems with that code including:
You're making long-running calls on the Swing event thread, something that will tie up this critical thread and is thus guaranteed to freeze your GUI.
Your calling paint(...) directly, something that should almost never be done.
I'll bet that most of your problem could be called by just making sure that your dialog is a modal JDialog, if you make long-running calls in a background thread such as with a SwingWorker, and rather than trying to wait for a lock to be released, use a call-back mechanism to notify the dialog to shut itself down.
For example:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;
/**
* http://stackoverflow.com/a/29933423/522444
* #author Pete
*
*/
#SuppressWarnings("serial")
public class TestMyDialog2 extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
public TestMyDialog2() {
add(new JButton(new MyDialogAction("Please press this button!", this)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
// let's make this reasonably big
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
TestMyDialog2 mainPanel = new TestMyDialog2();
JFrame frame = new JFrame("TestMyDialog2");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class MyDialogAction extends AbstractAction {
private JDialog dialog;
private MyWorker myWorker;
private TestMyDialog2 testMyDialog2;
public MyDialogAction(String name, TestMyDialog2 testMyDialog2) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
this.testMyDialog2 = testMyDialog2;
}
public void dialogIsClosing(WindowEvent e) {
if (myWorker != null && !myWorker.isDone()) {
myWorker.setKeepRunning(false);
} else {
if (dialog != null && dialog.isVisible()) {
dialog.dispose();
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
Window mainGui = SwingUtilities.getWindowAncestor(testMyDialog2);
dialog = new JDialog(mainGui, "My Dialog", ModalityType.APPLICATION_MODAL);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.add(Box.createRigidArea(new Dimension(200, 100)));
dialog.addWindowListener(new DialogWindowListener(this));
dialog.pack();
myWorker = new MyWorker();
myWorker.addPropertyChangeListener(new MyWorkerListener(dialog));
myWorker.execute();
dialog.setLocationRelativeTo(mainGui);
dialog.setVisible(true);
}
}
class MyWorker extends SwingWorker<Void, Void> {
private volatile AtomicBoolean keepRunning = new AtomicBoolean(true);
#Override
protected Void doInBackground() throws Exception {
// to emulate long-running code
while (keepRunning.get()) {
Thread.sleep(200);
System.out.println("Long running background code is running");
}
System.out.println("Doing shut-down process. Will close in 10 seconds");
for (int i = 0; i < 10; i++) {
System.out.println("Countdown: " + (10 - i));
Thread.sleep(1000); // emulate a long running shut-down process
}
return null;
}
public void setKeepRunning(boolean newValue) {
this.keepRunning.getAndSet(newValue);
}
}
class MyWorkerListener implements PropertyChangeListener {
private JDialog dialog;
public MyWorkerListener(JDialog dialog) {
this.dialog = dialog;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
dialog.dispose();
try {
((MyWorker) evt.getSource()).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
class DialogWindowListener extends WindowAdapter {
private MyDialogAction myDialogAction;
public DialogWindowListener(MyDialogAction myDialogAction) {
this.myDialogAction = myDialogAction;
}
#Override
public void windowClosing(WindowEvent e) {
myDialogAction.dialogIsClosing(e);
}
}
Related
I use Swing Application Framework in my program. And I have some long-time work. I use org.jdesktop.application.Task for it. Another programmer wrote two Tasks before I took this project (I can not ask him about the programm). When Tasks are executing user sees progress bar without showing percent complete, but what shows "Wait" message and user can not click to a main window while Task does not ended. It is fine! But I could not find place where ProgressBars was created. May be it is described in some xml-file or property-file?
Also I wrote another Tasks and when they run, progress bar which I created is not displayed or displayed incorrectly. I read about ProgressBar and ProgressMonitor, but it does not help me.
Programm continue to run after someTask.execute(), but I want to it displays ProgressBar, ProgressMonitor or something else and user can not click the main window and window will display correctly. Now window has black "blocks" when user change it.
May be I need use org.jdesktop.application.TaskMonitor. I try to use it as here https://kenai.com/projects/bsaf/sources/main/content/other/bsaf_nb/src/examples/StatusBar.java?rev=235 , but my main window is displayed incorrectly and my ProgressBar is not displayed.
I need to when Task is running program waits it, but user can see ProgressBar, can cancel the operation and can not click to the main window. How can I do it?
Here my code:
public class A{
#Action(name = "ActionName", block = Task.BlockingScope.APPLICATION)
public RequestInfoTask requestInfo() {
RequestInfoTask task = new RequestInfoTask(Application.getInstance());
isSuccessedGetInfo=false;
task.addTaskListener(new TaskListener.Adapter<List<InfoDTO>, Void>() {
#Override
public void succeeded(TaskEvent<List<InfoDTO>> listTaskEvent) {
isSuccessedGetResources=true;
}
});
//Here I want to the program shows ProgressMonitor and user can not click to the main window.
//But small window with message "Progress..." is displayed for several seconds and disappear.
ProgressMonitor monitor = new ProgressMonitor(getMainView(), "Wait! Wait!", "I am working!", 0, 100);
int progress = 0;
monitor.setProgress(progress);
while(!task.isDone()){
monitor.setProgress(progress+=5);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
monitor.setProgress(100);
//This code must run after "task" finishes.
if(isSuccessedGetInfo){
MyTask2 task2 = new MyTask2(Application.getInstance());
isSuccessedTask2=false;
task2.addTaskListener(new TaskListener.Adapter<Map<?,?>, Void>(){
#Override
public void succeeded(TaskEvent<Map<String, ICredential>> arg0) {
isSuccessedTask2=true;
}
});
//Do something with results of task2.
}
return task;
}
}
public class RequestInfoTask extends Task<List<InfoDTO>, Void> {
public RequestInfoTask(Application application) {
super(application);
}
#Override
protected List<InfoDTO> doInBackground() throws Exception {
List<InfoDTO> result = someLongerLastingMethod();
return result;
}
}
Part of your problem sounds like it comes from not using the EDT correctly. Any long running task needs to be started in it's own thread to keep the GUI responsive and repainting.
Ideally you'd be following a MVC pattern. In that case you place your Progress Bar in the view, your flag (that indicates whether the task should be running still) in the control, and your long running task in in the Model.
From that point, if your model checks periodically if it should stop (Probably at good stopping points), you can reset everything.
Here's an example with MVC:
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
public class ProgressBarDemo{
public static class View extends JPanel{
Controller control;
public JProgressBar progressBar = new JProgressBar(0, 100);
JButton button = new JButton("Start Long Running Task");
public View(Controller controlIn){
super();
this.control = controlIn;
setLayout(new BorderLayout());
button.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
//Toggle between running or not
if(control.isRunning){
control.isRunning = false;
button.setText("Canceling...");
button.setEnabled(false);
} else{
control.isRunning = true;
button.setText("Cancel Long Running Task");
control.startTask();
}
}});
progressBar.setStringPainted(true);
add(progressBar);
add(button, BorderLayout.SOUTH);
}
}
//Communications gateway
public static class Controller{
View view = new View(this);
boolean isRunning = false;
public void updateProgress(final int progress){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
view.progressBar.setValue(progress);
}});
}
public void reset(){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
isRunning = false;
view.button.setText("Start Long Running Task");
view.progressBar.setValue(0);
view.button.setEnabled(true);
}});
}
public void startTask(){
LongRunningClass task = new LongRunningClass(this);
new Thread(task).start();
}
}
public static class LongRunningClass implements Runnable{
Controller control;
public LongRunningClass(Controller reference){
this.control = reference;
}
#Override
public void run() {
try {
for(int i = 0; i < 11; i++){
//Monitor the is running flag to see if it should still run
if(control.isRunning == false){
control.reset();
break;
}
control.updateProgress(i * 10);
Thread.sleep(3000);
}
control.reset();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
// Create and set up the window.
JFrame frame = new JFrame("LabelDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add content to the window.
frame.add(new Controller().view);
// Display the window.
frame.pack();
frame.setVisible(true);
}
}
I need to run a background thread in my Java GUI that only runs when I click a button and pauses when I click that button again. I am not exactly sure how to set this up, but I have placed a thread in my constructor and the while loop within is set to go through when I set a specific boolean to TRUE. One button switches from setting this boolean TRUE or FALSE.
Everything else I have in this GUI works fine. When I tried debugging the thread, it actually works as I step through the thread but nothing when I try running the GUI completely. The GUI is rather large so I'm gonna put up a portion of the constructor and the action listener of the button. The rest of the code is unnecessary since it works just fine. I need to know what I am doing wrong here:
public BasketballGUI() {
// certain labels and buttons
Thread runningSim = new Thread() {
public void run() {
while(simRun) {
// do stuff here
}
}
};
runningSim.start();
}
// other GUI stuff
// actionListener that should run the thread.
class SimButtonListener implements ActionListener {
public void actionPerformed(ActionEvent arg0) {
if(!simRun) {
simRun = true;
sim.setText("Pause Simulator");
}
else if(simRun) {
simRun = false;
sim.setText("Run Simulator");
}
// other stuff in this actionListener
}
}
Establish a Swing based Timer with an ActionListener that will be called repeatedly.
In the actionPerformed(ActionEvent) method call repaint().
Start the timer (Timer.start()) when the user clicks Start
Stop the timer (Timer.stop()) when the user clicks Stop
If you cannot get it working from that description, I suggest you post an SSCCE of your best attempt.
I thought I had one 'lying around'.. Try this working SSCCE which uses images created in this SSCCE.
I could see this background thread useful for a Java GUI when handling button events to affect something like a text area or progress bar.
For the sake of argument, I will build you a tiny GUI that affects a Text Area. I hope this helps you.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;
public class TestClass extends JPanel {
super("TestClass - Title");
private AtomicBoolean paused;
private JTextArea jta;
private JButton btn;
private Thread thread;
public TestClass() {
paused = new AtomicBoolean(false);
jta = new JTextArea(100, 100);
btn = new JButton();
initialize();
}
public void initialize() {
jta.setLineWrap(true);
jta.setWrapStyleWord(true);
add(new JScrollPane(jta));
btn.setPreferredSize(new Dimension(100, 100));
btn.setText("Pause");
btn.addActionListener(new ButtonListener());
add(btn);
Runnable runnable = new Runnable() {
#Override
public void run() {
while(true) {
for(int i = 0; i < Integer.MAX_VALUE; i++) {
if(paused.get()) {
synchronized(thread) {
try {
thread.wait();
} catch(InterruptedException e) {
}
}
}
}
jta.append(Integer.toString(i) + ", ");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
};
thread = new Thread(runnable);
thread.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
if(!paused.get()) {
btn.setText("Start");
paused.set(true);
} else {
btn.setText("Pause");
paused.set(false);
synchronized(thread) {
thread.notify();
}
}
}
}
}
Main class to call everything.
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class MainClass {
public static void main(final String[] arg) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestClass());
frame.pack();
frame.setVisible(true);
frame.setLocationRelativeTo(null);
}
});
}
}
I did not test this code to see if it works exactly, Its main goal is to break you through your coders block and use my components to fix your issue. Hope this helped. Need anything else Email me at DesignatedSoftware#gmail.com
I was building a simple dialog, that is able to be dismissed by pressing a button or Esc button. I was using CountDownLatch to wait before dismissing the dialog, and called .countDown() from various listeners.
I ran into the following problem - pressing "X" on the window calls .countDown() and results in dialog dismissal, while pressing button results in calling the same code, but the thread does not resume execution. What could be the problem?
Compilable/runnable example:
import java.util.concurrent.CountDownLatch;
import javax.swing.JFrame;
public class StrangeDialog extends javax.swing.JDialog {
final CountDownLatch latch = new CountDownLatch(1);
public StrangeDialog(JFrame parent) {
super(parent, true); // removing this line fixes things
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosed(java.awt.event.WindowEvent evt) {
System.out.println(latch);
latch.countDown();
}
});
setFocusable(true);
addKeyListener(new java.awt.event.KeyAdapter() {
public void keyReleased(java.awt.event.KeyEvent e) {
System.out.println(latch);
latch.countDown();
}
});
setSize(100,100);
setVisible(true);
}
public static void main(String[] args) {
StrangeDialog dialog = new StrangeDialog(null);
try {
dialog.latch.await();
} catch (InterruptedException ex) {
}
dialog.setVisible(false);
System.out.println("Released");
}
}
You are creating a modal dialog. Basically the code does not progress past StrangeDialog dialog = new StrangeDialog(null); until the window closes.
try:
final StrangeDialog dialog = new StrangeDialog(null);
SwingUtilities.invokeLater(new Runnable() { public void run() { dialog.setVisible(true); } });
in main() to open the window and it will work as expected.
I need to stop user making multiple clicks on a JButton while the first click still execute.
I was able to came with a solution for this issue but I do not completelly understand why it's working.
Bellow I posted the code (trimmed to a minimum) that works and the one that does not work.
In first example (good) if you run it and click the button multiple times only one action is considered as for the second example (bad) if you click the mouse multiple times you get action executed at least twice.
The second (bad) example simply does not use invokeLater() method.
Where the difference in behaviour cames from?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
task.setEnabled(true);
task.setText("Test");
}
});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
And now the "wrong" code
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
//SwingUtilities.invokeLater(new Runnable() {
//public void run() {
task.setEnabled(true);
task.setText("Test");
//}
//});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
After info provided by #kleopatra and #Boris Pavlović here is the code I created that seems to work pretty decent.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
task.setText("Working...");
task.setEnabled(false);
SwingWorker worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Event " + evt + " name" + evt.getPropertyName() + " value " + evt.getNewValue());
if ("DONE".equals(evt.getNewValue().toString())) {
task.setEnabled(true);
task.setText("Test");
}
}
});
worker.execute();
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
you have two choises
1) JButton#setMultiClickThreshhold
2) you have to split this idea to the two separated actions inside actionListener or Action
1st. step, JButton#setEnabeld(false);
2nd. step, then call rest of code wrapped to the javax.swing.Action (from and dealyed by javax.swing.Timer), SwingWorker or Runnable#Thread
Okay, here's a code snippet using an Action
it disable's itself on performed
it spawns a task, at the end of which is enables itself again. Note: for simplicity here the task is simulated by a Timer, real-world would spawn a SwingWorker to do the background work, listening to its property changes and enable itself on receiving a done
set as the button's action
The code:
Action taskAction = new AbstractAction("Test") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action received ");
setEnabled(false);
putValue(NAME, "Working...");
startTask();
}
// simulate starting a task - here we simply use a Timer
// real-world code would spawn a SwingWorker
private void startTask() {
ActionListener l = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
putValue(NAME, "Test");
setEnabled(true);
}
};
Timer timer = new Timer(2000, l);
timer.setRepeats(false);
timer.start();
}};
JButton task = new JButton(taskAction);
There are two more ways.
You can define a flag. Set it when action start and reset back after the end. Check the flags in the actionPerformed. If inProgress==true just do nothing.
Another way is to remove the listener and assign it back after the action ends.
The right way is using a SwingWorker. When user click the button before submmiting a job to the SwingWorker the state of the button should be changed to disabled JButton#setEnabled(false). After the SwingWorker finished the job state of the button should be reset to enabled. Here's Oracle's tutorial on SwingWorker
After years of dealing with the frustration of this problem, I've implemented a solution that I think is the best.
First, why nothing else works:
JButton::setMutliclickThreshold() is not really an optimal solution, because (as you said) there is no way to know how long to set the threshold. This is only good to guard against double-click happy end-users because you have to set an arbitrary threshold.
JButton::setEnabled() is an obviously fragile solution that will only make life much more difficult.
So, I've created the SingletonSwingWorker. Now, Singletons are called anti-patterns, but if implemented properly, they can be a very powerful. Here is the code:
public abstract class SingletonSwingWorker extends SwingWorker {
abstract void initAndGo();
private static HashMap<Class, SingletonSwingWorker> workers;
public static void runWorker(SingletonSwingWorker newInstance) {
if(workers == null) {
workers = new HashMap<>();
}
if(!workers.containsKey(newInstance.getClass()) || workers.get(newInstance.getClass()).isDone()) {
workers.put(newInstance.getClass(), newInstance);
newInstance.initAndGo();
}
}
}
This will enable you to create classes which extend SingletonSwingWorker and guarantee only one instance of that class will be executable at one time. Here is an example implementation:
public static void main(String[] args) {
final JFrame frame = new JFrame();
JButton button = new JButton("Click");
button.setMultiClickThreshhold(5);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
DisplayText_Task.runWorker(new DisplayText_Task(frame));
}
});
JPanel panel = new JPanel();
panel.add(button);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class DisplayText_Task extends SingletonSwingWorker {
JFrame dialogOwner;
public DisplayText_Task(JFrame dialogOwner) {
this.dialogOwner = dialogOwner;
}
JDialog loadingDialog;
#Override
void initAndGo() {
loadingDialog = new JDialog(dialogOwner);
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(true);
loadingDialog.add(jpb);
loadingDialog.pack();
loadingDialog.setVisible(true);
execute(); // This must be put in the initAndGo() method or no-workie
}
#Override
protected Object doInBackground() throws Exception {
for(int i = 0; i < 100; i++) {
System.out.println(i);
Thread.sleep(200);
}
return null;
}
#Override
protected void done() {
if(!isCancelled()) {
try {
get();
} catch (ExecutionException | InterruptedException e) {
loadingDialog.dispose();
e.printStackTrace();
return;
}
loadingDialog.dispose();
} else
loadingDialog.dispose();
}
}
In my SwingWorker implementations, I like to load a JProgressBar, so I always do that before running doInBackground(). With this implementation, I load the JProgressBar inside the initAndGo() method and I also call execute(), which must be placed in the initAndGo() method or the class will not work.
Anyways, I think this is a good solution and it shouldn't be that hard to refactor code to refit your applications with it.
Very interested in feedback on this solution.
Note that when you are modifying anything in GUI your code must run on Event Dispatch thread using invokeLater or invokeAndWait if you are in another thread. So second example is incorrect as you are trying to modify enabled state from another thread and it can cause unpredictable bugs.
Is it possible to do this in a standard manner?
Here is the scenario.
Start doing something expensive in EDT (EDT is blocked till the expensive operation is over).
While EDT was blocked, the user kept on clicking/dragging the mouse buttons. All the mouse actions are recorded somewhere.
When EDT is free (done with the expensive stuff), it starts to process the mouse events.
What I want in step 3 is to discard the mouse events that have piled up. After the EDT is free, any new mouse event should be handled in the usual manner.
Any ideas on how to achieve this.
PS: It is not possible for me to prevent the EDT from getting blocked (I do not control the behavior of some of the modules in my program).
EDIT:
If I can safely call "SunToolkit.flushPendingEvents()" then I can always put a glasspane before starting the expensive operation in EDT. After the expensive operation is over then on the EDT thread, flush all the events - they will go to a glass pane that wont do anything. And then let EDT work as normal.
EDIT2:
I have added a SSCCE to demonstrate the issue.
public class BusyCursorTest2 extends javax.swing.JFrame {
public BusyCursorTest2() {
javax.swing.JButton wait = new javax.swing.JButton("Wait 3 seconds");
getContentPane().setLayout(new java.awt.GridLayout(2, 1, 0, 0));
getContentPane().add(wait);
getContentPane().add(new javax.swing.JToggleButton("Click me"));
setTitle("Busy Cursor");
setSize(300, 200);
setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
setVisible(true);
wait.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent event) {
final java.util.Timer timer = switchToBusyCursor(BusyCursorTest2.this);
try {
//do something expensive in EDT
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
//do nothing
}
} finally {
switchToNormalCursor(BusyCursorTest2.this, timer);
}
}
});
}
public static java.util.Timer switchToBusyCursor(final javax.swing.JFrame frame) {
startEventTrap(frame);
java.util.TimerTask timerTask = new java.util.TimerTask() {
public void run() {
startWaitCursor(frame);
}
};
final java.util.Timer timer = new java.util.Timer();
timer.schedule(timerTask, DELAY_MS);
return timer;
}
public static void switchToNormalCursor(final javax.swing.JFrame frame, final java.util.Timer timer) {
timer.cancel();
stopWaitCursor(frame);
stopEventTrap(frame);
}
private static void startWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.WAIT_CURSOR));
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.DEFAULT_CURSOR));
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static void startEventTrap(javax.swing.JFrame frame) {
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopEventTrap(javax.swing.JFrame frame) {
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static final java.awt.event.MouseAdapter mouseAdapter = new java.awt.event.MouseAdapter() {
};
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new BusyCursorTest2();
}
});
}
private static final int DELAY_MS = 250;
}
Run the SSCCE
Click on the button "Wait 3 seconds". It simulates an expensive operation. The mouse cursor will change to busy.
While the cursor is busy, click on the toggle button "Click me". If after three seconds, the toggle button changes its state, then the mouse event was received by the toggle button and was not trapped.
I want that while the cursor looks busy, the generated mouse (and other) events be discarded.
Thanks.
OK, I finally got everything to work. I am posting the SSCCE for a correctly working example. The trick is to hide the glasspane using "javax.swing.SwingUtilities.invokeLater()" method. Wrap the necessary code in a Runnable and then invoke it using invokeLater. In such a case, Swing processes all the mouse events (nothing happens since a glasspane intercepts them), and then hides the glasspane. Here is the SSCCE.
public class BusyCursorTest2 extends javax.swing.JFrame {
public BusyCursorTest2() {
javax.swing.JButton wait = new javax.swing.JButton("Wait 3 seconds");
getContentPane().setLayout(new java.awt.GridLayout(2, 1, 0, 0));
getContentPane().add(wait);
getContentPane().add(new javax.swing.JToggleButton("Click me"));
setTitle("Busy Cursor");
setSize(300, 200);
setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
setVisible(true);
wait.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent event) {
final java.util.Timer timer = switchToBusyCursor(BusyCursorTest2.this);
try {
//do something expensive in EDT or otherwise
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
//do nothing
}
} finally {
switchToNormalCursorEventThread(BusyCursorTest2.this, timer);
}
}
});
}
public static java.util.Timer switchToBusyCursor(final javax.swing.JFrame frame) {
startEventTrap(frame);
java.util.TimerTask timerTask = new java.util.TimerTask() {
public void run() {
startWaitCursor(frame);
}
};
final java.util.Timer timer = new java.util.Timer();
timer.schedule(timerTask, DELAY_MS);
return timer;
}
public static void switchToNormalCursorEventThread(final javax.swing.JFrame frame, final java.util.Timer timer) {
Runnable r = new Runnable() {
public void run() {
switchToNormalCursor(frame, timer);
}
};
javax.swing.SwingUtilities.invokeLater(r);
}
public static void switchToNormalCursor(final javax.swing.JFrame frame, final java.util.Timer timer) {
timer.cancel();
stopWaitCursor(frame);
stopEventTrap(frame);
}
private static void startWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.WAIT_CURSOR));
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.DEFAULT_CURSOR));
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static void startEventTrap(javax.swing.JFrame frame) {
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopEventTrap(javax.swing.JFrame frame) {
java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue();
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static final java.awt.event.MouseAdapter mouseAdapter = new java.awt.event.MouseAdapter() {
};
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new BusyCursorTest2();
}
});
}
private static final int DELAY_MS = 250;
}
Again, EDT if at all possible must not be blocked. But if you have to, you can have a working busy cursor as above.
Any comments are welcome.
Read this article.
Basically, long running tasks should not be done on the EDT. Java has provided SwingWorker for tasks such as that.
I'd go into more detail, but you don't tend to accept answers.
Definitely don't block the EDT. You should never do that!
Here's an easy utility class to do this (credit to Santosh Tiwari) :
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* When blocking the EDT (Event Queue) in swing, the cursor won't update, and windows won't render.
* This should show the hourglass even when you're blocking the EDT.
*
* Source:
* https://stackoverflow.com/questions/7085239/java-swing-clear-the-event-queue
*
* #author Kieveli, Santosh Tiwari
*
*/
public class BlockingWaitCursor {
private static final java.awt.event.MouseAdapter mouseAdapter = new java.awt.event.MouseAdapter() {};
/**
* The Dialog or main window is required to show the cursor and animate it. The actionListener is called
* as soon as initial setup is completed and the animation timer is running.
* #param currentComponent A panel, dialog, frame, or any other swing component that is the current focus
* #param action Your action to perform on the EDT. This is started extremely quickly and without delay.
*/
public static void showWaitAndRun(Component currentComponent, ActionListener action ) {
Timer timer = setupWaitCursor(currentComponent);
try {
// now allow our caller to execute their slow and delayed code on the EDT
ActionEvent event = new ActionEvent(BlockingWaitCursor.class, ActionEvent.ACTION_PERFORMED, "run");
action.actionPerformed(event);
}
finally {
resetWaitCursor(currentComponent, timer);
}
}
private static Timer setupWaitCursor(Component currentComponent) {
final Component glassPane = findGlassPane(currentComponent);
if ( glassPane == null ) {
return null;
}
// block mouse-actions with a glass pane that covers everything
glassPane.addMouseListener(mouseAdapter);
glassPane.setVisible(true);
// animate the wait cursor off of the EDT using a generic timer.
Timer timer = new Timer();
timer.schedule( new TimerTask() {
#Override
public void run() {
glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
glassPane.addMouseListener(mouseAdapter);
glassPane.setVisible(true);
}
}, 250l);
return timer;
}
private static void resetWaitCursor(Component currentComponent, final Timer timer) {
final Component glassPane = findGlassPane(currentComponent);
if ( glassPane == null ) {
return;
}
// Invoke later so that the event queue contains user actions to cancel while the loading occurred
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if ( timer != null )
timer.cancel();
Toolkit.getDefaultToolkit().getSystemEventQueue();
glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
glassPane.removeMouseListener(mouseAdapter);
glassPane.setVisible(false);
}
});
}
private static Component findGlassPane(Component currentComponent) {
// try to locate the glass pane by looking for a frame or dialog as an ancestor
JFrame frame = findFrame(currentComponent);
JDialog dialog = findDialog(currentComponent);
Component glassPane = null;
if ( frame != null )
glassPane = frame.getGlassPane();
if ( dialog != null )
glassPane = dialog.getGlassPane();
return glassPane;
}
private static JFrame findFrame(Component currentComponent) {
// find the frame if it exists - it may be the currentComponent
if ( currentComponent instanceof JFrame )
return (JFrame) currentComponent;
Window window = SwingUtilities.getWindowAncestor(currentComponent);
if ( window == null )
return null;
if ( ! (window instanceof JFrame) )
return null;
return (JFrame)window;
}
private static JDialog findDialog(Component currentComponent) {
// find the dialog if it exists - it may be the currentComponent
if ( currentComponent instanceof JDialog )
return (JDialog) currentComponent;
Window window = SwingUtilities.getWindowAncestor(currentComponent);
if ( window == null )
return null;
if ( ! (window instanceof JDialog) )
return null;
return (JDialog)window;
}
}
But never use it. Well, unless you're not proud and writing a quick utility that then got out of hand and became a main application, and you don't have time to pull your code apart to figure out what can run on a worker, and what would break because of integrations with swing / sql that are not thread-safe.