Java wait code until JFrame keyPressed - java

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

Related

Java mouse listener: toggle version "click counter"

I'm trying to make a basic click counter using mouse events in Java. I understand the tutorials everyone has but their programs are semi automatic and it registers one increment per click.
public void mouseClicked(MouseEvent e)
{
clicks++;
}
I'm trying to do a fully automatic version of that where it will continually increment until you release the button, but the release method doesn't switch the boolean to false and stop the loop. Any advice?
public class example{
private boolean fire = false;
public void mousePressed(MouseEvent e)
{
if(e.getButton()== e.BUTTON1){fire = true};
while(fire) {clickCounter++; }
}
}
public void mouseReleased(MouseEvent e){
fire = false;
}
}
Just to be clear, a "click" is what typically happens between the mouse been pressed and released, the idea that there are more "clicks" during this period is an artificial construct.
First, you need to go read Concurrency in Swing to better understand while "sleep" and while-loop won't work in this context. You're blocking the Event Dispatching Thread, preventing any new events from been processed.
The following example simply attempts to calculate the time between the mouse pressed and released event and applies a artificial multiplier to the result to create the "click" count
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.LocalDateTime;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JLabel label = new JLabel("...");
add(label);
addMouseListener(new MouseAdapter() {
private LocalDateTime clickTime;
#Override
public void mouseClicked(MouseEvent e) {
clickTime = LocalDateTime.now();
}
#Override
public void mouseReleased(MouseEvent e) {
if (clickTime == null) {
return;
}
Duration between = Duration.between(clickTime, LocalDateTime.now());
long seconds = between.getSeconds();
long clicks = seconds * 3;
label.setText("Held for " + seconds + "s = " + clicks + " clicks");
clickTime = null;
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Another solution might be to start a Swing Timer on mouse pressed and stop it on mouse released, allowing it to increment a value
You set fire= true then enter into an infinite while(true) loop.
To fix this, modify the boolean statement of the while loop inside the loop itself.
while(fire) {
count++;
if (mouseReleased(eventMouseIsReleased))
fire= false;
} // Check while(fire) but now it is false! Move on!
Hope this helps!

New JFrame is opening in disabled state when opened on a button click

I am stuck with a very unusual situation. I have a class "ScreenSizeSelector" which has a method 'getSelectedScreenSize'. The method's work is to create a UI, user drags the UI and method return back size of window.
Now I am calling the method of class in following ways:
A simple class (non GUI)
On the button click from a JFrame
In the first case, it is working perfectly fine (i.e. size selector window opens, user drags it, resize it and it is giving back window coordinates) but in second case, window opens but in disabled mode, user is not able to perform any operation on the window, not even able to close the window.
Here is the code I am using
ScreenSizeSelector class :
package screenrecorder;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.Border;
class ScreenSizeSelector {
private JFrame sizeSelectorWindow;
private JButton btnOk;
private Border emptyBorder;
private Rectangle screenArea = null;
private static Object lock = new Object();
public Rectangle getSelectedScreenSize(){
screenSizeSelectorUI();
Thread t = new Thread() {
public void run() {
synchronized(lock) {
while (sizeSelectorWindow.isVisible())
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
return screenArea;
}
public void screenSizeSelectorUI() {
emptyBorder = BorderFactory.createEmptyBorder();
sizeSelectorWindow = new JFrame("Select screen area");
btnOk = new JButton("Start");
sizeSelectorWindow.setUndecorated(true);
sizeSelectorWindow.getRootPane().setWindowDecorationStyle(3);
sizeSelectorWindow.setBackground( new Color(0, 0, 0, 0) );
sizeSelectorWindow.setSize(400,400);
sizeSelectorWindow.addWindowListener(new WindowEventHandler());
sizeSelectorWindow.setAlwaysOnTop(true);
sizeSelectorWindow.setLocationRelativeTo(null);
btnOk.setToolTipText("Click this button after deciding the screen area");
btnOk.addActionListener(new ButtonEventHandler());
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
buttonPanel.setBackground(new Color(0,0,0,0));
buttonPanel.add(btnOk);
sizeSelectorWindow.add(buttonPanel,BorderLayout.SOUTH);
sizeSelectorWindow.setVisible(true);
sizeSelectorWindow.setEnabled(true);
}
class ButtonEventHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
int x = (int)(sizeSelectorWindow.getBounds().getX());
int y = (int) (sizeSelectorWindow.getBounds().getY());
int width = sizeSelectorWindow.getWidth();
int height = sizeSelectorWindow.getHeight();
screenArea = new Rectangle(x,y,width,height);
sizeSelectorWindow.dispatchEvent(new WindowEvent(sizeSelectorWindow, WindowEvent.WINDOW_CLOSING));
}
}
class WindowEventHandler implements WindowListener{
#Override
public void windowOpened(WindowEvent e) {
}
#Override
public void windowClosing(WindowEvent e) {
synchronized (lock) {
sizeSelectorWindow.setVisible(false);
lock.notify();
}
}
#Override
public void windowClosed(WindowEvent e) {
}
#Override
public void windowIconified(WindowEvent e) {
sizeSelectorWindow.setState(JFrame.NORMAL);
Toolkit.getDefaultToolkit().beep();
}
#Override
public void windowDeiconified(WindowEvent e) {}
#Override
public void windowActivated(WindowEvent e) {}
#Override
public void windowDeactivated(WindowEvent e) {}
}
}
Test1 class :
package screenrecorder;
import java.awt.Rectangle;
public class Test1{
public static void main(String[] args){
System.out.println(new ScreenSizeSelector().getSelectedScreenSize());
}
}
Test2 class :
package screenrecorder;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Test2 extends JFrame{
public Test2(){
JButton btn = new JButton("Click ME");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(new ScreenSizeSelector().getSelectedScreenSize());
}
});
getContentPane().add(btn);
setSize(100,100);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args){
new Test2();
}
}
Any help is appreciated.
when you click the button, the action listener waits for the getSelectedScreenSize() function to return. and the getSelectedScreenSize() function is waiting for the second window created by screenSelectorUI() to be invisible. screenSelectorUI() does create a second window, but you set the color like this:
sizeSelectorWindow.setBackground( new Color(0, 0, 0, 0) );
if you look at the color constructor javadocs:
public Color(int r,
int g,
int b,
int a)
Creates an sRGB color with the specified red, green, blue, and alpha values in the range (0 - 255).
Parameters:
r - the red component
g - the green component
b - the blue component
a - the alpha component
you set the alpha value to 0, making it completely invisible. (alpha value is transparency) also, this second window is undecorated and does not exit on close, so you don't even know it's there at all.
what I don't get is how test1 worked at all.
side note: when I try test 1 on mac it only shows the button and all I can do is click it. the button will disappear, but the application will still be running.
This is basically a total guess, but a lot of the swing components make requests to the operating system, not commands. sort of like saying, "hey can I please be resized to 400, 400?" the OS doesn't technically have to do what you say. and I was reading How does Java handle multithreading? which says that multithreading really depends on the OS. I have a feeling it just messes up somewhere when screenSelectorUI() is called by itself, but somehow gets it right when it's inside the thread of some button.

JFrame doesn't appear with red background

I have strange problem when running my Java program.
It's designed to:
run external app specified in bat file and display fullscreen wallpaper
"hide" wallpaper for some time when buttons combination pressed
warn user that 5sec left so he can save work
when timeout occures display again fullscreen wallpaper and do some other stuff from bats
quit program when button combination pressed
warinng user is realized as displaying fullscreen red box for 200ms
I'm using visible function to do this.
It shows standard fullscreen frame discaring color settings. but only when I comment frame.setUndecorated(true). when uncommented I see only icon in taskbar.
On the other hand when I launch (Using BlueJ) only function visible
the red frame is displayed for specified amount of time. Simply standalone function works perfectly (in my oppinion) even if frame.setUndecorated(true) is used .
what can be wrong that I can't launch that red frame in fullscreen?
olympicApp class:
import java.awt.*;
import java.awt.Color;
import java.awt.event.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.Graphics;
import java.awt.image.*;
import java.io.*;
import java.io.IOException;
import javax.imageio.*;
import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JComponent;
public class olympicApp extends JComponent {
alertApp alert;
BufferedImage img;
public olympicApp()
{
try
{
img = ImageIO.read(new File("wallpaper.jpg"));
}
catch (IOException e)
{
}
}
public void paint(Graphics g)
{
g.drawImage(img, 0, 0, null);
}
public Dimension getPreferredSize()
{
if (img == null)
{
return new Dimension(200,200);
}
else
{
return new Dimension(img.getWidth(null), img.getHeight(null));
}
}
public static void visible()
{
JFrame frame = new JFrame();
frame.getContentPane().setBackground(Color.red);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.dispose();
//frame.setUndecorated(true);
frame.setAlwaysOnTop(true);
frame.pack();
frame.setVisible(true);
try
{
frame.setVisible(true);
Thread.sleep(500);
frame.setVisible(false);
}
catch(Exception ex)
{
}
frame.setAlwaysOnTop(false);
frame.setVisible(false);
}
public static void main(String[] args)
{
//alertApp reminder = new alertApp();
try
{
Process process = Runtime.getRuntime().exec("start-lv.bat");
Thread.sleep(500);
}
catch (IOException | InterruptedException e)
{
}
JFrame f = new JFrame("olympic");
f.setExtendedState(JFrame.MAXIMIZED_BOTH);
f.setUndecorated(true);
f.setAlwaysOnTop(true);
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.add(new olympicApp());
f.pack();
f.setVisible(true);
f.addKeyListener(new KeyListener()
{
public void keyPressed(KeyEvent kevt)
{
if(kevt.getKeyChar()=='l')
{
if(kevt.isAltDown())
{
f.setAlwaysOnTop(false);
f.setVisible(false);
try
{
Thread.sleep(5*1000);
visible();
Thread.sleep(5*1000);
//Process process = Runtime.getRuntime().exec("saving.bat");
Thread.sleep(500);
f.setAlwaysOnTop(true);
f.setVisible(true);
Process process2 = Runtime.getRuntime().exec("kopia.bat");
}
catch(IOException | InterruptedException e)
{
}
}
}
if(kevt.getKeyChar()=='q')
{
if(kevt.isAltDown())
{
System.exit(0);
}
}
}
public void keyTyped(KeyEvent kevt)
{
}
public void keyReleased(KeyEvent kevt)
{
}
});
}
}
I would imagine that you're going to have to do something in here...
public class alertApp {
public static void main(String[] args) {
// Sample loop to flash every 2 seconds
}
}
This is called when your program starts and where you should put the code to get the program running
I would also strogly recommend that you take a look at Code Conventions for the Java TM Programming Language, it will make it easier for people to read your code and for you to read others
As well as Concurrency in Swing and How to use Swing Timers which will help you solve other potential issues
I put neccessary code in visible funciton. I can see the frame but color information is discarted. Thanks for Concurrency and Timers, but I think the problem isn't connected to thread.sleep()
Then you didn't read the links...
public void keyPressed(KeyEvent kevt) {
if (kevt.getKeyChar() == 'l') {
if (kevt.isAltDown()) {
f.setAlwaysOnTop(false);
f.setVisible(false);
try {
Thread.sleep(5 * 1000);
visible();
Thread.sleep(5 * 1000);
//Process process = Runtime.getRuntime().exec("saving.bat");
Thread.sleep(500);
f.setAlwaysOnTop(true);
f.setVisible(true);
Process process2 = Runtime.getRuntime().exec("kopia.bat");
} catch (IOException | InterruptedException e) {
}
}
}
keyPressed is executed within the context of the Event Dispatching Thread, so when you call Thread.sleep, it prevents the EDT from processing the Event Queue, preventing it from painting or responding to other events

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

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