Why CountDownLatch sometimes doesn't release threads when count gets to zero? - java

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.

Related

How to programatically dismiss a message dialogue after a certain process has ended - Java

How could I show a JOptionPane at the beginning of a certain function, by having for example, "Working...", and then programmatically dismiss it at the end of said function/piece of code?
To answer your queston, you can run this piece of code:
//Declare the JOptionPane object
JOptionPane pane = new JOptionPane();
function yourFunction(){
//Beginning
pane.showInternalMessageDialog(frame, "Working...", "information", JOptionPane.INFORMATION_MESSAGE);
//End
pane.setVisible(false); //or pane.dispose();
}
The code can be enhanced by calling the pane.dispose() method when hiding the pane to release the system resources used by the pane.
Whenever a JOptionPane pops up it do so in a different thread so you can do it as mentioned below:
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import java.util.Scanner;
public class Main
{
private JOptionPane pane;
private JDialog dialog;
private Timer t;
public Main()
{
t = new Timer(2000, closeJDialog);
t.start();
pane = new JOptionPane("My Dialog Message to You!");
dialog = pane.createDialog("My Dialog Title");
dialog.setVisible(true);
}
private ActionListener closeJDialog = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
if (dialog.isShowing()) {
int i = new Scanner(System.in).nextInt();
// Call your function here
dialog.dispose();
System.out.println("dialog disposed");
}
}
};
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable() {
public void run()
{
new Main();
}
});
}
}

Java Swing Dialog, how to call wait method when capturing WindowClosing event

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);
}
}

How force the program wait a Task and show Progress Bar to user?

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);
}
}

JFrames method setVisible(true) or setVisible(false) not working in a loop. Frames are not displayed properly

JFrames not displaying properly in loop.
Code:-
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class SwingDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run()
{
final JFrame jfrm= new JFrame("A Simple Swing Application");
final JFrame jfrm2= new JFrame("A Simple Swing Application 2");
jfrm.setSize(275,100);
jfrm.setLocation(100,100);
jfrm2.setLocation(50,50);
jfrm2.setSize(275,100);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfrm2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel jlab = new JLabel("Swing means powerful GUIs.");
jfrm.add(jlab);
JButton button0= new JButton("loop");
jfrm.add(button0);
jfrm.setLayout(new FlowLayout());
JLabel jlab2 = new JLabel("Swing means powerful GUIs again");
jfrm2.add(jlab2);
//jfrm2.setVisible(true);
jfrm.setVisible(true);
button0.addActionListener(new ActionListener() {
private boolean confirmAction;
#Override
public void actionPerformed(ActionEvent e) {
confirmAction = true;
if (confirmAction) {
try {
while(true)
{
jfrm.setVisible(false);
jfrm2.setVisible(true);
try{
Thread.sleep(15000);
}
catch(InterruptedException ie)
{
System.out.println("nothing");
}
jfrm2.setVisible(false);
jfrm.setVisible(true);
}
} catch (Throwable t) {
t.printStackTrace(System.out);
}
}
}
});
}
});
}
}
If the problem is that they are not updating correctly (this is my only thought since you didn't explain the issue), you should try to include these methods in the loop:
validate();
repaint();
If your issue is different, please inform us.
The method
#Override
public void actionPerformed(ActionEvent e) {
...
}
is executed in the EDT. Once you have implemented an infinite loop inside EDT it will stop processing any farther events and your GUI will stop responding.
A possible solution may be to start a timer and on every timer tick post relevant events to EDT, using SwingUtilities.invokeLater() or invokeAndWait():
Replace the contents of actionPerformed(ActionEvent e) with the following:
Timer timer = new Timer(15000, new ActionListener() {
boolean flip = false;
#Override
public void actionPerformed(ActionEvent actionEvent) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
jfrm.setVisible(flip);
jfrm2.setVisible(!flip);
flip =! flip;
}
});
}
});
timer.start();

Disable JButton, while background job, to avoid multiple clicks

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.

Categories

Resources