public void actionPerformed(ActionEvent event)
{
// TODO Auto-generated method stub
JButton src = (JButton) event.getSource(); //get which button is clicked
if(src.equals(GO)) //if GO button is clicked
{
try {
runHack();
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(src.equals(STOP)) //if STOP button is clicked
{
//do nothing
FeedBack.setText(null);
FeedBack.setText("Stopped");
}
}
I have a program where when you click on a button GO, it will execute a method called runHack();
private void runHack() throws AWTException
{
FeedBack.setText(null);
FeedBack.setText("Running(This doesn't print out)");
while(true)//infinite loop
{
FeedBack.setText("This doesn't print out");
}
}
runHack() is method that runs an infinite loop. When I click on the GO button, the program freezes while executing the runHack() method. The String "Running" doesn't displayed on the JLabel FeedBack.
My question is how do you make events still available when the program is in the infinite loop? I want it so that when I press on the STOP button, the program exits out of the infinite loop. Also, I want the JLabel FeedBack to work inside the loop.
you need run this infine toop inside of a new thread .this is how to do using timer .swing timer runs in separate thread.set delay to zero.so it's act as a while(true) loop.
in your code you are blocking EDT because of long lasting task(infinite loop) .the changes you made to textfield not get update because EDT is blocked.
you need to swing timer not java.util.Timer
import import javax.swing.Timer;
declare timer
Timer t;//global declaration;
initialize //
t=new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
FeedBack.setText("This doesn't print out");
}
});
when button click//
JButton src = (JButton) event.getSource(); //get which button is clicked
if(src.equals(GO)) //if GO button is clicked
{
try {
t.start();
} catch (AWTException e) {
e.printStackTrace();
}
}
if(src.equals(STOP)) //if STOP button is clicked
{
//do nothing
t.stop();
FeedBack.setText(null);
FeedBack.setText("Stopped");
}
updating...
a complete example
import java.awt.AWTException;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.Timer;
public class example extends JFrame implements ActionListener{
Timer t;
private final JTextField FeedBack;
private JButton go;
private JButton stop;
int i=0;
public example() {
FeedBack=new JTextField("initial text");
go=new JButton("go");
stop=new JButton("stop");
go.addActionListener(this);
stop.addActionListener(this);
this.setLayout(new GridLayout(1, 3));
this.add(go);
this.add(stop);
this.add(FeedBack);
this.setVisible(true);
t = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
FeedBack.setText(i+"");
i++;
}
});
}
public void actionPerformed(ActionEvent event) {
JButton src = (JButton) event.getSource();
System.out.println(src);
if (src==go) //if GO button is clicked
{
t.start();
}
if (src==stop) //if STOP button is clicked
{
//stop timer
t.stop();
//FeedBack.setText(null);
FeedBack.setText("Stopped");
}
}
public static void main(String[] args) {
example f = new example();
}
}
output>>
Short answer:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run(){
runHack();
}
}).start();
Just take in account that there are better ways to spawn new Threads like using an Executor. Like someone commented, Threads is a broad topic, you should read the documentation.
[EDIT]
Like #Hovercraft Full Of Eels said is not Thread safe calling in a new Thread modifications of the UI.
Related
all
What I'm trying to do is to create a bouncing balls java program. Which I did. Each time the user presses start balls will populate the screen. the only problem I'm having is that I don't know how to pause it. Any help would be appreciated. I tried adding something similar to how I did the addball function but don't know how to apply that to pause the ball. I have tried to do the puase function by adding the button pause but don't know how to get it working
BounceFrame:
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BounceFrame extends JFrame {
private static final long serialVersionUID = 1L;
private BallComponent ballComponent;
public BounceFrame() {
setTitle("Bounce");
ballComponent = new BallComponent();
add(ballComponent, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
// Adds more balls.
addButton(buttonPanel, "Start", new ActionListener() {
public void actionPerformed(ActionEvent ae) {
addBall();
}
});
addButton(buttonPanel, "Pause", new ActionListener() {
public void actionPerformed(ActionEvent ae) {
}
});
// Closes the panel.
addButton(buttonPanel, "Close", new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.exit(0);
}
});
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
public void addButton(Container c, String title, ActionListener listener) {
JButton b = new JButton(title);
c.add(b);
b.addActionListener(listener);
}
public void addBall() {
Ball b = new Ball(ballComponent.getBounds());
RunnableBall rB = new RunnableBall(b, ballComponent);
Thread t = new Thread(rB);
t.start();
}
}
RunnableBall:
import java.util.logging.Level;
import java.util.logging.Logger;
public class RunnableBall implements Runnable {
private Ball b;
private BallComponent comp;
private static final int DELAY = 3; //Controls speed of the balls.
public RunnableBall(Ball b, BallComponent comp)
{
this.b = b;
this.comp = comp;
}
#Override
public void run() {
comp.add(b);
while (true)
{
b.move(comp.getBounds());
comp.repaint();
try
{
Thread.sleep(DELAY);
}
catch (InterruptedException ex) {
Logger.getLogger(RunnableBall.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
When you add the ball you need to store the RunnableBall object you are creating in some collection variable, such as a variable of type ArrayList<RunnableBall>, for example. Then in your Pause button's ActionListener you can loop through the ArrayList and call a pause method on each of your RunnableBalls.
So you'll need to then define a pause method inside RunnableBall, which sets a boolean variable "isPaused" to true. So then you'll need to create that variable, isPaused, inside the RunnableBall class, and make it change the behaviour of the run method. You should be able to figure that bit out.
One thing you'll need to take care with is the fact that because you're using multiple threads, you'll need the communication between those threads (i.e. the process of setting the isPaused variable to true or false) to be thread-safe. I think you could achieve that by declaring the isPaused variable to be volatile, but there are other ways to do it.
I'm using a JFrame and I wanted to display an image and pause the code until the user presses ANY key. After that key being pressed the image would close and the code would continue running.
What I did:
Created a flag
final boolean[] flag = {true};
Added a addKeyListener to the JFrame object that would change the flag
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
frame.setVisible(false);
frame.dispose();
flag[0] = false;
}
});
Wait loop until flagged
while (flag[0]){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
This is working, but I understand that it is a bit resourceful.
Is there any other way of making the wait loop? Is there any listener of the listener?
2nd try, using CountDownLatch:
Set the latch
final CountDownLatch latch = new CountDownLatch(1);
CountDown
for (JFrame frame : framesList) {
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
frame.setVisible(false);
frame.dispose();
latch.countDown();
}
});
Wait
latch.await();
So, you want to display an image and have the execution stop until the window is closed. This just screams modal dialog to me. A modal dialog will stop the code execution from where it is made visible, it will do it in such away so as not to block the Event Dispatching Thread and make your entire problem come to a screaming halt and hang the program. See How to use dialogs for more details...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Image;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
BufferedImage img = ImageIO.read(...);
ImageShower.show(null, img);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public static class ImageShower extends JPanel {
private JLabel label = new JLabel();
public ImageShower() {
setLayout(new BorderLayout());
add(label);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close");
am.put("close", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
Window window = SwingUtilities.windowForComponent(ImageShower.this);
if (window != null) {
window.dispose();
}
}
});
}
public void setImage(Image img) {
label.setIcon(new ImageIcon(img));
}
public static void show(Component owner, Image img) {
Window parent = null;
if (owner != null) {
parent = SwingUtilities.windowForComponent(owner);
}
JButton close = new JButton("Close");
close.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JButton btn = (JButton) e.getSource();
Window window = SwingUtilities.windowForComponent(btn);
if (window != null) {
window.dispose();
}
}
});
JDialog dialog = new JDialog(parent, Dialog.ModalityType.APPLICATION_MODAL);
ImageShower shower = new ImageShower();
shower.setImage(img);
dialog.add(shower);
dialog.add(close, BorderLayout.SOUTH);
dialog.getRootPane().setDefaultButton(close);
dialog.pack();
dialog.setLocationRelativeTo(owner);
dialog.setVisible(true);
}
}
}
"But wait, may images are large and take time to load and I don't want to freeze the UI while the load"...
Okay, for that, I'd look towards using a SwingWorker, which can load the image in the background but which provides simple methods for ensuring the the image is displayed within the context of the EDT properly...
public class ImageLoadAndShow extends SwingWorker<Void, Image> {
#Override
protected Void doInBackground() throws Exception {
BufferedImage img = ImageIO.read(...);
publish(img);
return null;
}
#Override
protected void process(List<Image> chunks) {
Image img = chunks.get(chunks.size() - 1);
ImageShower.show(null, img);
}
}
Not, if the image fails to load, you won't know about it, as the doInBackground method will pass the Exception out of the method. You'd need to use a combination of a PropertyChangeListener and the SwingWorkers get method to trap it, just remember, get is blocking, so calling it inside the context of the EDT will block until the worker completes
"But I need to carry out other operations when the dialog is closed"
There are a few ways you might be able to achieve this, depending on what it is you want to do, for this example, I've stuck with the SwingWorker, because it was easy to copy and paste the basic structure, but you could use a Runnable wrapped in a Thread
public class ImageLoadShowAndWait extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
BufferedImage img = ImageIO.read(...);
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
ImageShower.show(null, img);
}
});
return null;
}
}
Now, if none of that does what you want...then I'd like to know what it is you're actually doing :P, have a look at Foxtrot which provides an API which allows you to execute code asynchronisly within the EDT without blocking it (entirly), but which will stop the code execution at the point it's called until it completes
The thing is that I wanted it to close the JFrame when ANY key is pressed
KeyListener is going to give you issues, maybe not today, maybe not tomorrow, but it will blow up in your face eventually. The example I've provide binds the Escape key to dispose of the window. It also makes the "Close" button the default button, which provides Space and/or Enter keys as well and a nice visual queue to the user.
If you want to use KeyListener, that's up to you, but your core problem doesn't seem to revolve around it, but the ability to display a window and pause the code execution till it's closed
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();
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 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.