I want to open an Eclipse Wizard or MessageDialog in a new thread, but somehow I always get an exception like this one:
Exception in thread "Thread-7" org.eclipse.swt.SWTException: Invalid thread access
at org.eclipse.swt.SWT.error(SWT.java:4491)
at org.eclipse.swt.SWT.error(SWT.java:4406)
at org.eclipse.swt.SWT.error(SWT.java:4377)
at org.eclipse.swt.widgets.Widget.error(Widget.java:482)
at org.eclipse.swt.widgets.Shell.<init>(Shell.java:266)
at org.eclipse.swt.widgets.Shell.<init>(Shell.java:362)
at org.eclipse.jface.window.Window.createShell(Window.java:486)
at org.eclipse.jface.window.Window.create(Window.java:429)
at org.eclipse.jface.dialogs.Dialog.create(Dialog.java:1096)
at org.eclipse.jface.window.Window.open(Window.java:792)
at org.eclipse.jface.dialogs.MessageDialog.open(MessageDialog.java:330)
at de.uka.ipd.sdq.beagle.gui.GuiController$DialogPolling.run(GuiController.java:126)
at java.lang.Thread.run(Thread.java:745)
when using code like this:
/**
* Opens up the dialog displaying the actions "pause", "continue", and "abort" to the
* user. These actions are regarding the analysis.
*/
private void engageDialog() {
final String dialogTitle = "Beagle Analysis is Running";
final String dialogMessage = "Beagle Analysis is running.";
final String[] buttonLabels = {"Abort", "Pause"};
this.messageDialog =
new MessageDialog(this.shell, dialogTitle, null, dialogMessage, MessageDialog.INFORMATION, buttonLabels, 0);
new Thread(new DialogPolling()).start();
}
private class DialogPolling implements Runnable {
#Override
public void run() {
final int buttonClick = GuiController.this.messageDialog.open(); // line 126
if (buttonClick == 0) {
System.out.println("User clicked 'Abort'.");
}
if (buttonClick == 1) {
System.out.println("User clicked 'Pause'.");
}
}
}
This is from GuiController and line 126 is marked. Scroll to the right if you can't see the line number.
How can I open a Wizard or a MessageDialog in a new thread?
All wizards, dialogs, ... must be opened in the single SWT UI thread. You can use the Display.syncExec call in another thread to run the dialog open in the UI thread.
Display.getDefault().syncExec(runnable);
Your Runnable can call the dialog open and save the buttonClick value somewhere that you can access when syncExec returns.
GUI systems are usually design as single thread because its almost impossible to write multi thread GUI system. There is to many user interactions and too many events.
Thats why GUI framework usually create his own dedicated thread and all GUI activity is going in this thread. For example Swing has its AWT thread. If long running operation is executing in this thread, it causes freeze of the program (program doesn't react to user input). If you want to avoid this, you must run your logic in different thread. But only your logic, not the GUI actions!
There are some useful classes to solve this issues - like SwingWorked, that is design to run lengthy GUI-interaction tasks in a background thread.
.
Related
I have a section of my JAVA program where you click a button and the actionListener should go through the following process;
Change the text on the button from "Start" to "Standby"
Add a label to a panel stating that a process has started
Execute a method (that sorts data and returns it via addelement to a defaultListModel JList, and finally
Change the text on the button from "Start" to "Complete"
as per below
uploadNotamButton.addActionListener((ActionEvent e) -> {
if(e.getSource()==uploadNotamButton)
uploadNotamButton.setText("STANDBY");
progressLabel.setText("Process Has Begun, standby...");
progressLabel.setVisible(true);
uploadNotams();
uploadNotamButton.setText("COMPLETE");
});
However, when I press the button, the button text does not change, the label does not show, but the method executes. Only when the method is complete, does the button text change to "Complete" (never showed "STANDBY") and the label stating "the process has begun, standby" displays (when the process is complete).
Is this a feature of defaultlistmodel that takes priority over everything or my coding inexperience?
Also, the data that gets analysed in the method, is displayed in the JList in one go, and not each element at a time. If the data was shown in the list as it was analysed, it would at least show that something was happening. Is this not possible with the defaultListModel?
Many Thanks in advance
PG
Is this a feature of defaultlistmodel that takes priority over everything or my coding inexperience?
This has nothing to do with DefaultListModel and all to do with Swing being single-threaded. Your long running process is being run on the Swing event thread, blocking this thread from doing its necessary actions, including drawing text and images on your GUI and interacting with users.
The solution is to use a background thread such as can be obtained through a SwingWorker, running your long-running code in this background thread, adding a PropertyChangeListener to the worker to be notified when it's done, and then respond to this notification.
For example (code not tested)
uploadNotamButton.addActionListener((ActionEvent e) -> {
// if(e.getSource()==uploadNotamButton)
uploadNotamButton.setText("STANDBY");
progressLabel.setText("Process Has Begun, standby...");
progressLabel.setVisible(true);
// create worker to do background work
SwingWorker<Void, Void> worker = new SwingWorker<>() {
#Override
protected Void doInBackground() throws Exception {
// this is all done within a background thread
uploadNotams(); // don't make any Swing calls from within this method
return null;
}
};
// get notified when the worker is done, and respond to it
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue == SwingWorker.StateValue.DONE) {
uploadNotamButton.setText("COMPLETE");
// the code below needs to be surrounded by a try/catch block
// and you'll need to handle any exceptions that might be caught
((SwingWorker) evt.getSource()).get();
}
}
});
worker.execute(); // run the worker
});
I have a mainClass in Java, that starts a GUI in swing. I ask the user to open a file using a JFileChooser. I want the main to wait until the user has finished picking the file and then continue with the rest of the code in main. How do I do this using threads? Thanks in advance.
Here is the skeleton code:
public class MainClass {
public static void main(String[] args) {
GUI gui= new GUI();
//wait for user input here
//Continue with code
System.out.println("User has picked a file");
}
}
GUI.java
public class GUI{
//User picks file using JFileChooser
JFileChooser chooseFile= new JFileChooser();
//Notify mainclass we're done with fiction to continue with code
}
OK, Two things.
You don't need multiple threads
The thing is, you can accomplish your goal of waiting for a user to select a file simply by using a Modal dialog. This works about like the following:
import javax.swing.*;
public class DialogTest {
public static void main(String[] args) {
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
System.out.println("File chooser is now closed. File is: " +
chooser.getSelectedFile().toString());
}
}
The showOpenDialog method will not return until the user has either selected a file, clicked cancel, or else clicked the X. Just be aware that getSelectedFile() will return null if the user cancels.
If you do need threads (you know, for something else)
Swing uses what it calls the Event Dispatch Thread. Swing is not thread safe, as mentioned in the comment. What this means is that any and all method calls to Swing components should be done from the EDT. You can schedule code to be run on the EDT by using SwingUtilities.invokeLater(Runnable). You can schedule something to run in a background thread (using a thread pool) by using a Swing Worker. Most of your code will probably just run on the EDT. Long-running operations can be sent to a background thread using swing workers.
I'm using a ProgressMonitorDialog with an IRunnableWithProgress to read a file in the background.
If an error occurs during this file processing (the data isn't what I'm expecting), I would like to ask the user if s/he wants to continue with the next line.
The problem is now that in order to ask the user if they want to continue, I'd have to show a dialog. Showing a dialog from a non-UI thread involves using Display.asyncExec() or Display.syncExec(). In order to return the result (user decision) to the background thread I'd have to use a callback.
Now, the problem is, that when I get the result in a callback in the background thread, how can I continue reading the file? Or, in other words, how can I pause the execution of the background thread until the feedback returns and then continue it?
I'm open to suggestions and willing to restructure my environment to accommodate this behaviour.
Dislay.syncExec blocks the thread you call it from so you can do something like:
final int[] result = new int[] {0};
display.syncExec(new Runnable() {
public void run() {
Shell shell = display.getActiveShell();
MessageDialog dialog = .... your message dialog
result[0] = dialog.open();
}
});
... dialog return code in result[0]
(heavily adapted from code in org.eclipse.equinox.internal.p2.ui.ValidationDialogServiceUI)
I'm using JFace to write a simple file-explorer application. The application's logic can be simplified as:
Display contents of a folder in a TableViewer.
Whenever a folder item gets double-clicked, async-load (to keep UI responsive) its contents and display it.
So in my opnion, there are at least 2 threads get involved: a) the UI thread and b) the background thread that fetches contents of a folder.
What really bothers me here is how does the two threads communicate and do I have to 'invent the wheel'? To be more specific:
How to tell the background thread when an item gets double-clicked? I suppose I need a task queue shared between the two threads or does JFace already provides some async-task mechanism?
How to tell the UI thread that the data have arrived and repaint the table? Which one to choose, asyncexec or syncexec?
What I would usually do is something like this:
// On double-click, start a new thread
new Thread(new Runnable()
{
#Override
public void run()
{
// Get your new data in this thread
final MyFancyDataObject data = SomeOtherClass.goAndGetMyData();
// Update the GUI, this is the safe way to do it from a non-gui-thread
Display.getCurrent().asyncExec(new Runnable()
{
public void run()
{
GuiClass.updateContent(data);
}
});
}
}).start();
I need to enable/disable the Finish button on the WizardDialog depending on some field that is present in a panel residing within a WizardPage.
Following is the code to open the wizard dialog:
UserWizardDialog dialog = new UserWizardDialog(window.getShell(), new UserWizard(window.getShell()));
dialog.open();
Inside this UserWizard, there is a WizardPage called CustomerWizardPage which has a CustomerPanel. In this panel, I have Customer PIN field, depending on whose value, I have to enable/disable the Finish button on the UserWizardDialog.
In the ItemListener event of that field in the panel, I add the below code:
parent.getWizard().getContainer().updateButtons();
But it gives exception:
org.eclipse.swt.SWTException: Invalid thread access
at org.eclipse.swt.SWT.error(SWT.java:3884)
at org.eclipse.swt.SWT.error(SWT.java:3799)
at org.eclipse.swt.SWT.error(SWT.java:3770)
at org.eclipse.swt.widgets.Widget.error(Widget.java:463)
at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:355)
at org.eclipse.swt.widgets.Control.setEnabled(Control.java:2923)
at org.eclipse.jface.wizard.WizardDialog.updateButtons(WizardDialog.java:1257)
at com.noi.rac.dhl.eco.util.components.CustomerPanel$4.itemStateChanged(CustomerPanel.java:304)
"Invalid thread access" happens when you modify the UI components in the Non-UI Thread.
To avoid this exception, you should modify the UI components in the UI thread. In your source code, I think you should do update your buttons in the UI Thread.
For example:
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
parent.getWizard().getContainer().updateButtons();
}
});
You can use syncExec() or asyncExec() based on your needs.
Hope this will help you.