I have a message label and a submit button. The submit button will be pressed multiple times, and the action for the each press can take up to a minute.
When the button is pressed, I want to set the message to empty, and after the task is complete, I want to set the message to "Complete".
private void submitActionPerformed(java.awt.event.ActionEvent evt) {
message = "";
updateMessageLabel();
doTheTask();
/* this update is apply to the label after completion */
message = "Complete";
}
Is it possible to update that message label before the submitActionPerformed() method is run (or in the method), but after the the button is clicked?
Although the Swing concurrency tutorial already contains some very good samples on how to deal with concurrency in Swing, find below an example which
contains a checkbox to prove the UI is still alive
has a progress bar, which gets updated from the SwingWorker
has a label, which gets updated once the SwingWorker is finished
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class SwingWorkerExample {
private static JProgressBar PROGRESS_BAR;
private static JLabel OUTPUT_LABEL;
private static JFrame createGUI(){
JFrame testFrame = new JFrame( "TestFrame" );
PROGRESS_BAR = new JProgressBar( );
PROGRESS_BAR.setMinimum( 0 );
PROGRESS_BAR.setMaximum( 100 );
OUTPUT_LABEL = new JLabel( "Processing" );
testFrame.getContentPane().add( PROGRESS_BAR, BorderLayout.CENTER );
testFrame.getContentPane().add( OUTPUT_LABEL, BorderLayout.SOUTH );
//add a checkbox as well to proof the UI is still responsive
testFrame.getContentPane().add( new JCheckBox( "Click me to proof UI is responsive" ), BorderLayout.NORTH );
testFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
return testFrame;
}
public static void main( String[] args ) throws InvocationTargetException, InterruptedException {
EventQueue.invokeAndWait( new Runnable() {
#Override
public void run() {
JFrame frame = createGUI();
frame.pack();
frame.setVisible( true );
}
} );
//start the SwingWorker outside the EDT
MySwingWorker worker = new MySwingWorker( PROGRESS_BAR, OUTPUT_LABEL );
worker.execute();
}
private static class MySwingWorker extends SwingWorker<String, Double>{
private final JProgressBar fProgressBar;
private final JLabel fLabel;
private MySwingWorker( JProgressBar aProgressBar, JLabel aLabel ) {
fProgressBar = aProgressBar;
fLabel = aLabel;
}
#Override
protected String doInBackground() throws Exception {
int maxNumber = 10;
for( int i = 0; i < maxNumber; i++ ){
Thread.sleep( 2000 );//simulate long running process
double factor = ((double)(i+1) / maxNumber);
System.out.println("Intermediate results ready");
publish( factor );//publish the progress
}
return "Finished";
}
#Override
protected void process( List<Double> aDoubles ) {
//update the percentage of the progress bar that is done
int amount = fProgressBar.getMaximum() - fProgressBar.getMinimum();
fProgressBar.setValue( ( int ) (fProgressBar.getMinimum() + ( amount * aDoubles.get( aDoubles.size() - 1 ))) );
}
#Override
protected void done() {
try {
fLabel.setText( get() );
} catch ( InterruptedException e ) {
e.printStackTrace();
} catch ( ExecutionException e ) {
e.printStackTrace();
}
}
}
}
Yes you can do this using SwingWorker thread, do all the pre submitActionPerformed() activities like updating the label, in the execute() method of the currentThread and call doTheTask() as a background job using worker Thread.
I suggest you to go through this documentation for reference about SwingWorker Thread
Related
I am trying to update an ImageIcon on a JLabel which sits on a JLayeredPane, but there is a lot of latency between when the setting thread sends the proper state to the JLabel object and when the GUI displays the ImageIcon of the proper state. The following code is an example of the issue, look for the difference in time between the print of the button being on/off and when the displayed icon gets lighter/darker.
The setting thread:
new Thread(new Runnable() { // setting thread
#Override
public void run() {
// TODO Auto-generated method stub
try {
while(true) {
System.out.println("testButton on"); // print that the button is on
testButton.updateState(1); // set button state to on
Thread.sleep(70 + random.nextInt(500)); //sleep between 70 and 570 milliseconds
System.out.println("testButton off");// print that the button is off
testButton.updateState(0); // set button state to off
Thread.sleep(70 + random.nextInt(500)); // sleep between 70 and 570 milliseconds
}
} catch(Exception e) {
e.printStackTrace();
}
}
}).start();
The button object:
class Button extends JLabel {
ImageIcon released;
ImageIcon pressed;
String text;
public Button(int x, int y, String text) {
released = new ImageIcon("src/components/images/button.png");
pressed = new ImageIcon("src/components/images/buttonDown.png");
setBounds(x,y, 100, 100);
this.text = text;
setIcon(released);
}
public void updateState(int data) {
if (data == 1) {
setIcon(pressed);
}
else {
setIcon(released);
}
}
}
The ImageIcons are only 325 bytes, so what might be causing the latency? I looked up about the Event Dispatcher Thread and many people say it should be instantaneous for an image to get painted.
End goal: Have many button objects on screen with the setting thread calling them to update based on randomly occurring actions. The displayed icon for a specific button object should change immediately as it is set in the function. The setting thread will not be constantly looping, instead loop once for every action sent (it is twice here just to show the issue).
Any suggestions or things to try I will test as soon as I can.
Edit: In the end the thread that gets the information will call to a device driver in Linux where it will wait for a response and only when it gets a response will it need to update the window. From what I know timer is used to update something at regular intervals, but I am likely wrong.
As explained in the comments running long processes on the The Event Dispatch Thread blocks it, so it does not respond to changes.
Also you are not suppose to update Swing components from other (not EDT) threads.
You need to use Swing tools like SwingWorker or Timer.
The following mcve demonstrates a simple slide-show using Timer:
import java.awt.BorderLayout;
import java.io.IOException;
import java.net.URL;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.Timer;
public class ChangeButtonIcon extends JPanel{
private final URL[] urls = {
new URL("https://findicons.com/files/icons/345/summer/128/cake.png"),
new URL("http://icons.iconarchive.com/icons/atyourservice/service-categories/128/Sweets-icon.png"),
new URL("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_FkBgG3_ux0kCbfG8mcRHvdk1dYbZYsm2SFMS01YvA6B_zfH_kg"),
};
private int iconNumber = 0;
private final JButton button;
private boolean stop = true;
private final Random random;
private static final int MIN_DELAY = 70, DELAY = 500;
private Timer timer;
public ChangeButtonIcon() throws IOException {
random = new Random();
button = new JButton();
button.setIcon(new ImageIcon(urls[iconNumber]));
button.setHorizontalTextPosition(SwingConstants.CENTER);
button.addActionListener(e -> startStopSlideShow());
add(button);
}
private void startStopSlideShow(){
stop = ! stop;
if(stop){
timer.stop();
return;
}
timer = new Timer( MIN_DELAY+ random.nextInt(DELAY), (e)->swapIcon());
timer.start();
}
private void swapIcon() {
iconNumber = iconNumber >= urls.length -1 ? 0 : iconNumber+1;
button.setIcon(new ImageIcon(urls[iconNumber]));
}
public static void main(String[] args) throws IOException{
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.add(new ChangeButtonIcon());
window.add(new JLabel("Click image to start / stop"), BorderLayout.PAGE_END);
window.pack();
window.setVisible(true);
}
}
I'm a beginner in java so sorry if I'm asking a stupid question , but how do I make a new thread in my gui class that would create a progress bar. I have a class named progress and made a new thread in my gui class using the constructor that I have created. But for some reason, I am getting a strange error:
"constructor progress in class NewJFrame.progress cannot be applied to given types;
required: no arguments
found: JProgressBar
reason: actual and formal argument lists differ in length
NewJframe.java
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
if (jRadioButton1.isSelected()){
App m = new App();
Thread t1 = new Thread(new progress(jProgressBar1));
m.sendPingRequest2("104.160.142.3",jTextPane1,jTextPane2,jTextField1);
}
}
progress.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author User
*/
import javax.swing.JProgressBar;
public class progress implements Runnable {
private static int DELAY = 500;
JProgressBar progressBar;
public progress (JProgressBar bar) {
progressBar = bar;
}
public void run() {
int minimum = progressBar.getMinimum();
int maximum = progressBar.getMaximum();
for (int i = minimum; i < maximum; i++) {
try {
int value = progressBar.getValue();
progressBar.setValue(value + 1);
Thread.sleep(DELAY);
} catch (InterruptedException ignoredException) {
}
}
}
}
This:
Thread t1= new progress ( jProgressBar1);
Should be:
Thread t1 = new Thread(new progress(jProgressBar1));
since your progress class implements Runnable and does not extend Thread.
Also your error message is strange:
constructor progress in class NewJFrame.progress cannot be applied to given types
suggesting that the problem resides within the constructor of the NewJFrame.progress class, a class that looks to be nested within the NewJFrame class. If this is so, get rid of the nested class and only deal with the free-standing progress (re-name it "Progress" please) class.
But having said that, your code has potential problems as you're changing the state of the JProgressBar, a Swing component, directly from within a background thread, and this is not Swing thread-safe. Much better to use a SwingWorker and link it to the JProgressBar's state as per the JProgressBar standard tutorial (check the link please).
For example:
import java.awt.event.KeyEvent;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class ProgressFun extends JPanel {
private JProgressBar progressBar = new JProgressBar(0, 100);
public ProgressFun() {
progressBar.setStringPainted(true);
final JButton startProgress = new JButton("Start Progress");
startProgress.setMnemonic(KeyEvent.VK_S);
startProgress.addActionListener(l -> {
startProgress.setEnabled(false);
progressBar.setValue(0);
final MyWorker myWorker = new MyWorker();
myWorker.execute();
myWorker.addPropertyChangeListener(pcEvent -> {
if (pcEvent.getPropertyName().equals("progress")) {
int value = (int) pcEvent.getNewValue();
progressBar.setValue(value);
} else if (pcEvent.getNewValue() == SwingWorker.StateValue.DONE) {
startProgress.setEnabled(true);
try {
myWorker.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
});
});
add(progressBar);
add(startProgress);
}
private static void createAndShowGui() {
ProgressFun mainPanel = new ProgressFun();
JFrame frame = new JFrame("Progress Fun");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MyWorker extends SwingWorker<Void, Integer> {
#Override
protected Void doInBackground() throws Exception {
int progress = 0;
setProgress(progress);
while (progress < 100) {
progress += (int)(5 * Math.random());
progress = Math.min(progress, 100);
TimeUnit.MILLISECONDS.sleep((int) (500 * Math.random()));
setProgress(progress);
}
return null;
}
}
As an aside, you will want to learn and use Java naming conventions. Variable names should all begin with a lower letter while class names with an upper case letter. Learning this and following this will allow us to better understand your code, and would allow you to better understand the code of others.
I call a method which lists all the files in a directory, and adds them to a JTable:
addFilesWithSubsButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
reverseLoadingVisibility(loaderLabel); //set Visible
addFilesWithSubs2(chooser, loaderLabel);
}
});
public void addFilesWithSubs2(JFileChooser chooser, JLabel loaderLabel) {
//loading all files ....
//when every file is listed:
//Set invisible
reverseLoadingVisibility(loaderLabel);
}
The another method change reverse the visibility of the JLabel in which the loading .gif is.
public void reverseLoadingVisibility(JLabel loaderLabel) {
loaderLabel.setVisible(!loaderLabel.isVisible());
}
The problem is: the gif doesn't play, freezes while the files are added to the JTable.
UPDATE: Still have problem the loading gif freezes
addFilesButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
reverseLoadingVisibility(loaderLabel);
try {
new AddFiles().doInBackground(
chooser, CHOOSERTITLE,
lastDictionary,
sdf,
filesTable,
model,
columnNames,
loaderLabel);
} catch (Exception e) {
e.printStackTrace();
}
}
});
public class AddFiles extends SwingWorker{
#Override
protected Void doInBackground() throws Exception {
return null;
}
protected void doInBackground(JFileChooser chooser, String CHOOSERTITLE,
String lastDictionary,
SimpleDateFormat sdf,
JTable filesTable,
DefaultTableModel model,
String[] columnNames,
JLabel loaderLabel) throws Exception {
//Set visible
reverseLoadingVisibility(loaderLabel);
chooser.setDialogTitle(CHOOSERTITLE);
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
//
chooser.setAcceptAllFileFilterUsed(true);
//TODO: this changed to chooser
if (chooser.showOpenDialog(chooser) == JFileChooser.APPROVE_OPTION) {
// create a file that is really a directory
File aDirectory = new File(chooser.getSelectedFile().toString());
lastDictionary = chooser.getSelectedFile().toString();
// get a listing of all files in the directory
String[] filesInDir = aDirectory.list();
// TODO
System.out.println("Number of files: " + filesInDir.length);
// have everything i need, just print it now
for ( int i=0; i<filesInDir.length; i++ )
{
File currentFile = new File(aDirectory + "\\" + filesInDir[i]);
System.out.println(filesInDir[i] );
System.out.println(aDirectory );
System.out.println(currentFile.length()/1024 + " KB");
System.out.println(sdf.format((currentFile).lastModified()));
// Avoid duplicates
int row = 0;
boolean duplicate = false;
for (; row < filesTable.getRowCount(); row++) {
if (model.getValueAt(row, 1).equals(filesInDir[i]) &&
model.getValueAt(row, 3).equals(aDirectory)
) {
duplicate = true;
break;
}
System.out.println("model.getValueAt(row, 1) " + model.getValueAt(row, 1));
System.out.println(filesInDir[i]);
System.out.println("model.getValueAt(row, 3) " + model.getValueAt(row, 3));
System.out.println(aDirectory);
}
if (!duplicate && currentFile.isFile()) {
model.addRow(new Object[]{
filesTable.getRowCount()+1,
filesInDir[i],
null,
aDirectory,
currentFile.length()/1024 + " KB",
sdf.format((currentFile).lastModified())
});
}
}
}
else {
System.out.println("No Selection ");
}
// Readjust columns
adjustTableColumns(filesTable, columnNames);
//Set unvisible
reverseLoadingVisibility(loaderLabel);
}
...
That's why because all files are loaded in the EDT (Event Dispatch Thread) (hopefully you launch your application using SwingUtilities.invokerLater() method) which cause all swing components to freeze. For more details read this java document by oracle: Initial Threads.
In order to solve your problem, you have to use a SwingWorker. A class responsible for heavy background tasks in Swing applications. With a simple google search, you can take an idea from here: How do I use SwingWorker in Java?
UPDATE in order to answer OP's comment.
The truth is that your code is a little bit big, and the most important, it is not an SSCCE.
In order to give you one more hand to find the solution you are looking for, i have created an SSCCE, using a SwingWorker that does something "heavy". In our case, the something heavy, is to write 1000 lines in a .txt file, but each line, thread (our worker) will sleep for 10ms.
Take it a look, run it if you want (i recommend it). Some extra comments inside the code, do not forget to check them.
package test;
import java.awt.BorderLayout;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class SwingWorkerExample extends JFrame {
/*
* Worker has Void doInBackground a.k.a, doInBackground method needs to return nothing.
* Worker needs to process-publish Integers.
*/
private SwingWorker<Void, Integer> writeToFileWorker = null;
private JLabel gifLabel;
private JButton doSomethingHeavy;
public SwingWorkerExample() {
super("Just a test.");
createWorker();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
gifLabel = new JLabel();
ImageIcon gif = new ImageIcon("C:/Users/George/Desktop/giphy.gif");
gifLabel.setIcon(gif);
gifLabel.setVisible(false); //Initialy non visible
gifLabel.setHorizontalTextPosition(JLabel.CENTER);
gifLabel.setVerticalTextPosition(JLabel.BOTTOM);
gifLabel.setHorizontalAlignment(JLabel.CENTER);
getContentPane().add(gifLabel, BorderLayout.CENTER);
doSomethingHeavy = new JButton("Do something heavy in another thread and start dancing...");
doSomethingHeavy.addActionListener(e -> {
//Before start the worker, show gif and disable the button
doSomethingHeavy.setEnabled(false);
gifLabel.setVisible(true);
writeToFileWorker.execute();
});
getContentPane().add(doSomethingHeavy, BorderLayout.PAGE_END);
setSize(500, 300);
setLocationRelativeTo(null);
}
private void createWorker() {
writeToFileWorker = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
File fileToWrite = new File(System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "hello_worlds.txt");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileToWrite));) {
for (int line = 0; line < 1000; line++) {
writer.append("Hello World! My name is Swing Worker.");
writer.append(System.lineSeparator());
Thread.sleep(10);
publish(line);
}
}
return null;
}
/*
* Runs in Event Dispatch Thread (EDT)
*/
#Override
protected void process(List<Integer> chunks) {
int line = chunks.get(0);//First parameter is the line
gifLabel.setText("Written " + line + " lines in the txt.");
super.process(chunks);
}
/*
* Runs in Event Dispatch Thread (EDT)
*/
#Override
protected void done() {
//When swing worker is finished, a.k.a the heavy work, stop the gif and enable the button
gifLabel.setVisible(false);
doSomethingHeavy.setEnabled(true);
super.done();
}
};
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SwingWorkerExample swe = new SwingWorkerExample();
swe.setVisible(true);
});
}
}
Small preview:
My guess would be that your addFilesWithSubs2-Method is blocking the UI thread. If you have long running tasks, than you have to execute them in a separate thread e.g. SwingWorker
Let's say you have a TextView that displays an number like 0 and you have a Button.
Now if the user presses the Button the Number in the TextView will increase for one (this i know how to do) but if the user presses the Button and don't release it then the number in the TextView should be increased an this should repeat it self as long the user don't release the Button.
In other words: How to increase the Number over and over again as long the user holds down the Button?
A general approach (not specific to Android) would be to detect the press and release event separately. The press event starts a periodic task (Runnable or Thread) which adds to the counter (let us say 5 times a second, or once every 200 ms). The release event stops the periodic task.
You'll need to schedule an asynchronous repeating event when you receive a mousePressed event and stop it when you receive a mouseReleased event.
There's lots of ways to handle this in Java. I like using the java.util.concurrent classes, which are quite flexible. There's a few things to keep in mind though:
If your asynchronous events don't happen on the Event Dispatch Thread, you need to set the JButton text using SwingUtilities.invokeLater().
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Frame
{
public static void main( String[] args )
{
JFrame frame = new JFrame( );
final JButton button = new JButton( "0" );
final ScheduledExecutorService executor = Executors.newScheduledThreadPool( 1 );
button.addMouseListener( new MouseAdapter( )
{
int counter = 0;
ScheduledFuture<?> future;
#Override
public void mousePressed( MouseEvent e )
{
Runnable runnable = new Runnable( )
{
public void run( )
{
SwingUtilities.invokeLater( new Runnable( )
{
public void run( )
{
button.setText( String.valueOf( counter++ ) );
}
} );
}
};
future = executor.scheduleAtFixedRate( runnable, 0, 200, TimeUnit.MILLISECONDS );
}
#Override
public void mouseReleased( MouseEvent e )
{
if ( future != null )
{
future.cancel( true );
}
}
} );
frame.add( button );
frame.setSize( 400, 400 );
frame.setVisible( true );
}
}
Set up a View.OnLongClickListener for your button
Give your activity a Runnable, and initialize (but don't start it) when you load the activity
Have the OnLongClickListener do its regular async task of updating the textbox and checking the time since it was first clicked
Create an OnTouchListener that pauses the Runnable when the touch event is realeased.
I know that's a rough draft, but this is a really useful pattern to be able to reuse and modify, so it's worth sinking your talons into it...
Is it possible to do this in a standard manner?
Here is the scenario.
Start doing something expensive in EDT (EDT is blocked till the expensive operation is over).
While EDT was blocked, the user kept on clicking/dragging the mouse buttons. All the mouse actions are recorded somewhere.
When EDT is free (done with the expensive stuff), it starts to process the mouse events.
What I want in step 3 is to discard the mouse events that have piled up. After the EDT is free, any new mouse event should be handled in the usual manner.
Any ideas on how to achieve this.
PS: It is not possible for me to prevent the EDT from getting blocked (I do not control the behavior of some of the modules in my program).
EDIT:
If I can safely call "SunToolkit.flushPendingEvents()" then I can always put a glasspane before starting the expensive operation in EDT. After the expensive operation is over then on the EDT thread, flush all the events - they will go to a glass pane that wont do anything. And then let EDT work as normal.
EDIT2:
I have added a SSCCE to demonstrate the issue.
public class BusyCursorTest2 extends javax.swing.JFrame {
public BusyCursorTest2() {
javax.swing.JButton wait = new javax.swing.JButton("Wait 3 seconds");
getContentPane().setLayout(new java.awt.GridLayout(2, 1, 0, 0));
getContentPane().add(wait);
getContentPane().add(new javax.swing.JToggleButton("Click me"));
setTitle("Busy Cursor");
setSize(300, 200);
setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
setVisible(true);
wait.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent event) {
final java.util.Timer timer = switchToBusyCursor(BusyCursorTest2.this);
try {
//do something expensive in EDT
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
//do nothing
}
} finally {
switchToNormalCursor(BusyCursorTest2.this, timer);
}
}
});
}
public static java.util.Timer switchToBusyCursor(final javax.swing.JFrame frame) {
startEventTrap(frame);
java.util.TimerTask timerTask = new java.util.TimerTask() {
public void run() {
startWaitCursor(frame);
}
};
final java.util.Timer timer = new java.util.Timer();
timer.schedule(timerTask, DELAY_MS);
return timer;
}
public static void switchToNormalCursor(final javax.swing.JFrame frame, final java.util.Timer timer) {
timer.cancel();
stopWaitCursor(frame);
stopEventTrap(frame);
}
private static void startWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.WAIT_CURSOR));
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.DEFAULT_CURSOR));
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static void startEventTrap(javax.swing.JFrame frame) {
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopEventTrap(javax.swing.JFrame frame) {
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static final java.awt.event.MouseAdapter mouseAdapter = new java.awt.event.MouseAdapter() {
};
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new BusyCursorTest2();
}
});
}
private static final int DELAY_MS = 250;
}
Run the SSCCE
Click on the button "Wait 3 seconds". It simulates an expensive operation. The mouse cursor will change to busy.
While the cursor is busy, click on the toggle button "Click me". If after three seconds, the toggle button changes its state, then the mouse event was received by the toggle button and was not trapped.
I want that while the cursor looks busy, the generated mouse (and other) events be discarded.
Thanks.
OK, I finally got everything to work. I am posting the SSCCE for a correctly working example. The trick is to hide the glasspane using "javax.swing.SwingUtilities.invokeLater()" method. Wrap the necessary code in a Runnable and then invoke it using invokeLater. In such a case, Swing processes all the mouse events (nothing happens since a glasspane intercepts them), and then hides the glasspane. Here is the SSCCE.
public class BusyCursorTest2 extends javax.swing.JFrame {
public BusyCursorTest2() {
javax.swing.JButton wait = new javax.swing.JButton("Wait 3 seconds");
getContentPane().setLayout(new java.awt.GridLayout(2, 1, 0, 0));
getContentPane().add(wait);
getContentPane().add(new javax.swing.JToggleButton("Click me"));
setTitle("Busy Cursor");
setSize(300, 200);
setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
setVisible(true);
wait.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent event) {
final java.util.Timer timer = switchToBusyCursor(BusyCursorTest2.this);
try {
//do something expensive in EDT or otherwise
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
//do nothing
}
} finally {
switchToNormalCursorEventThread(BusyCursorTest2.this, timer);
}
}
});
}
public static java.util.Timer switchToBusyCursor(final javax.swing.JFrame frame) {
startEventTrap(frame);
java.util.TimerTask timerTask = new java.util.TimerTask() {
public void run() {
startWaitCursor(frame);
}
};
final java.util.Timer timer = new java.util.Timer();
timer.schedule(timerTask, DELAY_MS);
return timer;
}
public static void switchToNormalCursorEventThread(final javax.swing.JFrame frame, final java.util.Timer timer) {
Runnable r = new Runnable() {
public void run() {
switchToNormalCursor(frame, timer);
}
};
javax.swing.SwingUtilities.invokeLater(r);
}
public static void switchToNormalCursor(final javax.swing.JFrame frame, final java.util.Timer timer) {
timer.cancel();
stopWaitCursor(frame);
stopEventTrap(frame);
}
private static void startWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.WAIT_CURSOR));
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopWaitCursor(javax.swing.JFrame frame) {
frame.getGlassPane().setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.DEFAULT_CURSOR));
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static void startEventTrap(javax.swing.JFrame frame) {
frame.getGlassPane().addMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(true);
}
private static void stopEventTrap(javax.swing.JFrame frame) {
java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue();
frame.getGlassPane().removeMouseListener(mouseAdapter);
frame.getGlassPane().setVisible(false);
}
private static final java.awt.event.MouseAdapter mouseAdapter = new java.awt.event.MouseAdapter() {
};
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new BusyCursorTest2();
}
});
}
private static final int DELAY_MS = 250;
}
Again, EDT if at all possible must not be blocked. But if you have to, you can have a working busy cursor as above.
Any comments are welcome.
Read this article.
Basically, long running tasks should not be done on the EDT. Java has provided SwingWorker for tasks such as that.
I'd go into more detail, but you don't tend to accept answers.
Definitely don't block the EDT. You should never do that!
Here's an easy utility class to do this (credit to Santosh Tiwari) :
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* When blocking the EDT (Event Queue) in swing, the cursor won't update, and windows won't render.
* This should show the hourglass even when you're blocking the EDT.
*
* Source:
* https://stackoverflow.com/questions/7085239/java-swing-clear-the-event-queue
*
* #author Kieveli, Santosh Tiwari
*
*/
public class BlockingWaitCursor {
private static final java.awt.event.MouseAdapter mouseAdapter = new java.awt.event.MouseAdapter() {};
/**
* The Dialog or main window is required to show the cursor and animate it. The actionListener is called
* as soon as initial setup is completed and the animation timer is running.
* #param currentComponent A panel, dialog, frame, or any other swing component that is the current focus
* #param action Your action to perform on the EDT. This is started extremely quickly and without delay.
*/
public static void showWaitAndRun(Component currentComponent, ActionListener action ) {
Timer timer = setupWaitCursor(currentComponent);
try {
// now allow our caller to execute their slow and delayed code on the EDT
ActionEvent event = new ActionEvent(BlockingWaitCursor.class, ActionEvent.ACTION_PERFORMED, "run");
action.actionPerformed(event);
}
finally {
resetWaitCursor(currentComponent, timer);
}
}
private static Timer setupWaitCursor(Component currentComponent) {
final Component glassPane = findGlassPane(currentComponent);
if ( glassPane == null ) {
return null;
}
// block mouse-actions with a glass pane that covers everything
glassPane.addMouseListener(mouseAdapter);
glassPane.setVisible(true);
// animate the wait cursor off of the EDT using a generic timer.
Timer timer = new Timer();
timer.schedule( new TimerTask() {
#Override
public void run() {
glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
glassPane.addMouseListener(mouseAdapter);
glassPane.setVisible(true);
}
}, 250l);
return timer;
}
private static void resetWaitCursor(Component currentComponent, final Timer timer) {
final Component glassPane = findGlassPane(currentComponent);
if ( glassPane == null ) {
return;
}
// Invoke later so that the event queue contains user actions to cancel while the loading occurred
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if ( timer != null )
timer.cancel();
Toolkit.getDefaultToolkit().getSystemEventQueue();
glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
glassPane.removeMouseListener(mouseAdapter);
glassPane.setVisible(false);
}
});
}
private static Component findGlassPane(Component currentComponent) {
// try to locate the glass pane by looking for a frame or dialog as an ancestor
JFrame frame = findFrame(currentComponent);
JDialog dialog = findDialog(currentComponent);
Component glassPane = null;
if ( frame != null )
glassPane = frame.getGlassPane();
if ( dialog != null )
glassPane = dialog.getGlassPane();
return glassPane;
}
private static JFrame findFrame(Component currentComponent) {
// find the frame if it exists - it may be the currentComponent
if ( currentComponent instanceof JFrame )
return (JFrame) currentComponent;
Window window = SwingUtilities.getWindowAncestor(currentComponent);
if ( window == null )
return null;
if ( ! (window instanceof JFrame) )
return null;
return (JFrame)window;
}
private static JDialog findDialog(Component currentComponent) {
// find the dialog if it exists - it may be the currentComponent
if ( currentComponent instanceof JDialog )
return (JDialog) currentComponent;
Window window = SwingUtilities.getWindowAncestor(currentComponent);
if ( window == null )
return null;
if ( ! (window instanceof JDialog) )
return null;
return (JDialog)window;
}
}
But never use it. Well, unless you're not proud and writing a quick utility that then got out of hand and became a main application, and you don't have time to pull your code apart to figure out what can run on a worker, and what would break because of integrations with swing / sql that are not thread-safe.