In my java application I am using swing to implement the UI. There is a button called theButton which is engaged with some IO operation in the following timely steps :
the button originally has the text "Click to connect"
then before the connect operation starts I want the theButton reads
"Connecting in progress..."
then the IO operation gets started
once the IO operation is done theButton now reads "connected ( click to disconnect)".
Issue:
I am using the following code, but first of all the button's text doesn't change to "Connecting in progress..." before the IO starts! as well button doenst actually get disabled before the IO starts! What should I do here?
--
// theButton with text "Click to connect is clicked"
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
theButton.setText("Trying to connect...");
theButton.setEnabled(false);// to avoid clicking several times! Some users cannot wait
theButton.repaint();
// doing some IO operation which takes few seconds
theButton.setText("connected ( click to disconnect)");
theButton.setEnabled(true);
theButton.repaint();
}
});
Your problem is here:
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
theButton.setText("Trying to connect...");
theButton.setEnabled(false);
theButton.repaint();
// doing some IO operation which takes few seconds // **********
theButton.setText("connected ( click to disconnect)");
theButton.setEnabled(true);
theButton.repaint();
}
});
The code marked with the ******* comment is running on the EDT and will tie it up freezing your app and all it's painting.
Use a SwingWorker instead to run the code in a background thread.
Note that there is no need to use invokeLater(...) for code in an ActionListener since this code is already running on the EDT by default.
Also get rid of your repaint() calls since they aren't needed and they don't help.
Add a PropertyChangeListener to your SwingWorker to listen for when it is done, and then you can reset your JButton.
Instead do:
// code not compiled nor tested
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
theButton.setText("Trying to connect...");
theButton.setEnabled(false);
MySwingWorker mySwingWorker = new MySwingWorker();
mySwingWorker.addPropertyChangeListener(new PropertyChangeListener() {
// listen for when SwingWorker's state is done
// and reset your button.
public void propertyChange(PropertyChangeEvent pcEvt) {
if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
theButton.setText("connected ( click to disconnect)");
theButton.setEnabled(true);
}
}
});
mySwingWorker.execute();
}
});
and
// code not compiled nor tested
public class MySwingWorker extends SwingWorker<Void, Void> {
#Override
public void doInBackground() throws Exception {
// doing some IO operation which takes few seconds
return null;
}
}
And be sure to read: Concurrency in Swing.
Related
Trying to get rid of this SWTException: Invalid thread access. Generated from within a JButton ActionListener. Ultimate intent is to have button open a Browser window, navigates to a URL and then URL is brought back to opening dialog...
private static final Display display = Display.getDefault();
// Fired from JButton:
class ShowBrowserAction implements java.awt.event.ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// Place-holder UI Update...
display.asyncExec(new Runnable() {
public void run() {
System.out.println("Async task run");
}
});
// Blocking until UI element is done...
while (!display.isDisposed()) {
// Always gives a thread access error, but still calls async event:
if ( !display.readAndDispatch() )
display.sleep();
}
}
}
Thoughts?
You can only call SWT actions on the SWT UI thread. Calling them anywhere else is not supported.
Use syncExec rather than asyncExec if you want to wait for a UI runnable to complete.
You say it seems to work, but this will vary on different platforms. For example on macOS it will definitely fail completely.
I have a problem with a long running task.
After my dialog is shown I want to scan a ftp directory. This task takes some time so I need to run this task no in the UI thread.
My idea was
#Override
protected void postDialogOpen() {
// if invoked via menu button
if (!scanFtp) {
final Display display = Display.getDefault();
new Thread(new Runnable() {
#Override
public void run() {
//initProgressWaitViewer();
scanFtpServer();
//closeProgressWaitViewer();
display.syncExec(new Runnable() {
#Override
public void run() {
updateTree();
}
});
}
}).run();
}
}
But during the execution of scanFtpServer() my dialog is not movable and if I click on it it becomes "unresponsible".
Is there something I am doing wrong?
When calling method run() in class Thread, you are executing the method on the caller thread, just like calling any other method. If you want to spawn a new thread and execute method run() in that thread, you need to call method start() instead, that will do all the work of setting up the thread and running it.
So replace
}).run();
with
}).start();
Try to do .start() instead of .run() .
I am trying to do the following: click a button, button disappears for 2 seconds, text appears for 2 seconds and after those 2 seconds the visibility is reversed. So far I have done this:
btnScan.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
txtScanning.setVisible(true);
btnScan.setVisible(false);
try {
Thread.sleep(2000); //1000 milliseconds is one second.
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
btnScan.setVisible(true);
}
});
and the result is that as soon as I click the btnScan, the whole program freezes for 2 seconds before doing anything. How do I add the delay at the correct order?
You should not call sleep method in your code that dispatches the event. All the work related UI is handled by EDT(Event Dispatch Thread) and a sleep method will cause it to freeze and hence your Swing application will freeze.
To overcome it you should use a Timer. Run the timer and execute the UI manipulation using SwingUtilities.invokeLater so that it is handled by EDT.
import java.util.Timer;
// make it a member variable
Timer timer = new Timer();
........
public void actionPerformed(java.awt.event.ActionEvent evt) {
button.setVisible(false);
timer.schedule(new TimerTask() {
#Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
button.setVisible(true);
}
});
}
}, 2000);
}
Currently in your code, you are causing the EDT (event dispatcher thread) to pause with the invocation of Thread.sleep
Performing any long running tasks in the EDT will cause your UI to freeze.
To achieve what you desire, use a SwingWorker thread to perform your actions
This might help: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
Swing is a single threaded environment, anything that blocks this thread will prevent it from processing new events, including repaint requests.
Swing is also not thread safe, meaning img you should never create or update the UI from outside the context of the EDT.
In this case you can use a Swing Timer to trigger a callback to occur at some time in the future which (the notification) will be executed within the context of the EDT, making it safe to update the UI with
Take a look at Concurrency in Swing and How to us Swing Timers for more details
Making use of Swing timer, you can do something like this:
btnScan.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
txtScanning.setVisible(true);
btnScan.setVisible(false);
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent acv) {
btnScan.setVisible(true);
txtScanning.setVisible(false);
}
});
// setRepeats(false) to make the timer stop after sending the first event
timer.setRepeats(false);
timer.start();
}
});
I want to run my program where the value of a label changes after the Timer goes off. But whenever the Timer runs I will keep getting the Invalid Thread access error and my label does not get updated.
protected void createContents() {
<--GUI codes -->
//Timer set to go every 10 seconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Timer");
lblState.setText("On");
}
};
new Timer(delay, taskPerformer).start();
}
This link from the SWT FAQ explains the error and how to solve it: any code that modifies GUI components (in your case, setting the text of the label) needs to run on the display thread, otherwise this error will occur.
To run on the display thread, wrap the code inside a Runnable and call Display.getDefault().syncExec( with the provided Runnable:
Display.getDefault().syncExec(new Runnable() {
public void run() {
// code that affects the GUI
}
});
All access to UI objects must be done in the user interface thread. You can do this using Display.asyncExec (or Display.syncExec).
Change your line:
lblState.setText("On");
to
Display.getDefault().asyncExec(() -> lblState.setText("On"));
for Java 8. For Java 7 or earlier use:
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run()
{
lblState.setText("On");
}
});
When I click Print Button it should show a Gif Animation followed by the text "Working..."
but here only the text "Working..." appears , not the animation.
Here's the Code:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
jLabel1.setVisible(true);
/* This portion is Time Consuming so I want to display a Loading gif animation. */
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
empPrint=new HashMap();
if(!empPrint.isEmpty())
empPrint.clear();
if(jRadioButton1.isSelected())
empPrint.put("PNO",parent.emp.getPAN());
else
empPrint.put("PNO",records.get(jComboBox1.getSelectedItem()));
REPORT="Report.jrxml";
try {
JASP_REP =JasperCompileManager.compileReport(REPORT);
JASP_PRINT=JasperFillManager.fillReport(JASP_REP,empPrint,parent.di.con);
JASP_VIEW=new JasperViewer(JASP_PRINT,false);
JASP_VIEW.setVisible(true);
JASP_VIEW.toFront();
}
catch (JRException excp) {
}
setVisible(false);
}
});
}
You should use a SwingWorker for time consuming tasks. Using invokeLater() just pushes it to the event queue, and it gets run in the EDT, blocking it.
Drawing in swing is done in the event dispatch thread, but since the EDT is busy running your printing task, swing has no chance to process repaint requests.
// Note the upped case "Void"s
SwingWorker worker = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() {
// Do the printing task here
return null;
}
#Override
public void done() {
// Update the UI to show the task is completed
}
}.execute();
The SwingUtilities.invokeLater() method will not help you in this case. The Runnable you pass still gets executed on the Event Dispatch Thread (EDT, the thread responsible for drawing the UI and responding to clicks etc.).
You could look at SwingWorkers, but you could just as well use a simple ExecutorService and pass the Runnable to there. The Executor framework - which was added in Java 5 or 6 - offers relatively simple to use tools to have stuff run in the background without having to worry about your own threads. I recommend going with something like this (pseudo code):
private ExecutorService executor = Executors.newFixedExecutorService()
....
public void buttonPressed() {
label.setVisible(true);
...
executor.submit(new Runnable() {
// create the report etc.
// DO NOT ACCESS ANY UI COMPONENTS FROM HERE ANYMORE!
// ...
SwingUtilities.invokeLater(new Runnable() {
// update the UI in here
label.setVisible(false);
});
});
}
As you can see, SwingUtilities.invokeLater is used here, too. However, it is called from a background thread to make sure your UI code gets executed on the EDT instead of on the background thread. That is what it is designed for, because UI components must never be accessed (not even read from!) from a background thread. That way you have a convenient mechanism to update you label nevertheless. You could also use it to update some progress bar etc.