EventQueue.invokeLater with multiple swing UIs - java

There's a nice discussion on EventQueue.invokeLater() here.
I have a controller class, Master() that loads two UI windows in my application. For example:
public class Master(){
public Master(){
aView = new subView();
bView = new subView();
Where subView extends JFrame and has the following main method:
public class SubView extends JFrame{
....
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new SubView();
}
});}
}
Notice that SubView.main() uses the invokeLater(). My question is how can I invokeLater() within master? Something like:
public class Master(){
public Master(){
aView = EventQueue.invokeLater(new subView);
bView = EventQueue.invokeLater(new subView);
It's not this simple because invokeLater does not return anything. Furthermore, because it's "invoked later", the values of aView and bView remain null in Master. Is there anyway to invoke both in the same manner that main() would invoke one of them in the runLater thread?

I would use invokeAndWait as you need to wait for the outcome.
SubView aView, bView;
public Master() {
java.awt.EventQueue.invokeAndWait(new Runnable() {
public void run() {
aView = new SubView();
bView = new SubView();
}
});
// aView and bView will be initialised.
}

It may prove awkward to solve this problem by invoking instances of Runnable. As an alternative use a SwingWorker to update the table models of both the master and detail views. This example may be a useful staring point.

Following Peter's suggestion, I did the following:
I created an intermediate Runnable called RunUIThread that returns that exposes the objects aView and bView, so I can return them to my Master program. Peter, do you think this is valid?
RunUIThread uiThread = new RunUIThread();
try {
java.awt.EventQueue.invokeAndWait(uiThread);
} catch (InterruptedException | InvocationTargetException ex) {
LOG.error("MasterView interrupted or failed to invoke RunUIThread");
}
aView = uiThread.getaView();
bView = uiThread.getbView();

Related

Java Swing - Updating the view from multiple thread

This question may be duplicated because I found a lot of similar question, but not the answer to my problem: I need to update the view of my SWING application from different SwingWorker.
I have a View class with a JTextArea and a JTable that i need to update during the execution of the Threads. The view also has a Start button that launch all the threads.
The controller listens for the button to be clicked then launch the threads:
public class MonitorPageController {
private MonitorPage monitorPage;
private List<Mission> missions;
class StartButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < missions.size(); i++) {
MyWorker worker = new MyWorker(missions.get(i));
worker.execute();
}
}
}
}
Then I have MyWorker class that manage the model:
public class MyWorker extends SwingWorker<Integer, String> {
private Mission m;
//<dec>
Block block1 = new Block();
Block block2 = new block();
Block block3 = new Block();
Block block4 = new Block();
public MyWorker(Mission mission) {
this.m = mission;
}
#Override
protected Integer doInBackground() throws Exception {
//<exe>
block1.addObserver(block2);
block2.addObserver(block3);
block3.addObserver(block4);
block4.addObserver(block2);
block1.update(null, m);
return 4;
}
}
In the end I have the Block class that is where I need to update the GUI (JTable and JTextArea):
public class Block extends Node implements Observer {
public Mission run(Mission m) {
m.setStatus(Mission.UNEXECUTED);
// HERE I WANT TO NOTIGY THE VIEW OF THE CHANGE OF STATUS OF THE MISSION
return m;
}
#Override
public void update(Observable o, Object arg) {
Mission m = this.run((Mission) arg);
setChanged();
notifyObservers(m);
}
}
EDIT: Mission is a simple class with the attribute: int status
I already tried with another observer pattern: I setted the mission as observable and the MonitorPageController as the observer. Then in the setter method of the status in class Mission I added the setChanged() and the notifyObservers() methods. In the end in the Observer (MonitoPageController) I implemented the update() method to call the view and update the gui.
I liked this way because it's clean and easy to implement, but I don't now why, after calling the notifyObserver() nothing was happening, so I discarded this solution, even if it seems to be the right one
Update the UI calling SwingUtilities.invokeLater() :
public class Block extends Node implements Observer {
public Mission run(Mission m) {
m.setStatus(Mission.UNEXECUTED);
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
//UPDATE UI HERE
}
});
return m;
}
#Override
public void update(Observable o, Object arg) {
Mission m = this.run((Mission) arg);
setChanged();
notifyObservers(m);
}
}
I found a possible solution, maybe there better ideas, but this works for now:
Creating the SwingWorker in MonitorPageController I changed the constructor passing the istance of the MonitorPageController too.
MyWorker worker = new MyWorker(misssions.get(i), this);
Then in MyWorker class when I create Block1, Block2, ...I pass them the istance of the MyWorker:
Block block1 = new Block(this);
Block block2 = new Block(this);
....
In the same class (MyWorker) I created a method:
public void log(Mission m, String s) {
controller.log(m, s);
}
Controller is the istance of the MonitorPageController that created the worker.
Now the inside block class, when I want to notify the change of status, I can call:
parentWorker.log(mission, "some string");
In the end the log() method in the MonitorPageController calls the view method to update the components...by now it seems to work...

Why doesn't JTable update its view when adding new rows to DefaultTableModel?

I have weird problems with JTable. Method addRows doesn't force jTable to update its view:
public class VirusTable extends JTable {
private String[] columnNames = { "", "Virus", "Path", "Size", "Created", "Last Mofified" };
DefaultTableModel tableModel;
public VirusTable() {
super();
tableModel = (DefaultTableModel) super.getModel();
tableModel.setColumnIdentifiers(columnNames);
// super.setFillsViewportHeight(true);
// super.setIntercellSpacing(new Dimension(5, 5));
// super.setRowSelectionAllowed(false);
// super.setColumnSelectionAllowed(false);
}
public void addRows(Collection<UserFile> viruses) {
System.out.println("AddRows=" + viruses);
for (UserFile virus : viruses) {
tableModel.addRow(virus.toRowData());
}
// tableModel.fireTableDataChanged();
// tableModel.fireTableStructureChanged();
int rowCount = super.getRowCount();
// super.revalidate();
// super.repaint();
System.out.println("rowCount=" + rowCount);
}
}
addRows method is called from SwingWorker.process method.
Interestingly that rowCount is always correct but rows are not displayed on JTable.
I tried everything and nothing helps.
I wanted to create short runnable demo but I cannot implement this bug.
I posted this project on github and described what is wrong there.
camickr is correct, there are actually two instances of your VirusTable (and in fact MainFrame as well, but only one of them is visible) being created. You've provided a way to access the MainFrame object by basically turning it into a singleton, but without the constraint that only one instance can ever be created. IMHO, you could've done this a lot better without creating a static getInstance method in your MainFrame class.
Anyhoo, in your main method, you do this:
public static void main(String[] args) throws IOException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new MainFrame().setVisible(true);
}
});
}
But because of the way you've coded your application, you should be doing this (not that I recommend this approach):
public static void main(String[] args) throws IOException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
MainFrame.getInstance().setVisible(true);
}
});
}
Thank camickr, not me.
Maybe you are making changes to a Swing model from a thread other than the event dispatch thread (hard to tell from your code)? All Swing interactions must be done from the EDT, so either wrap your table update in SwingUtilities.invokeLater() (if you want it to update as each entry is found) or add the items to the table model in the done() method of SwingWorker which forces the execution on the EDT.

Simple way to wait for a method to finish before starting another one

I'm not familiar at all with Java threading :(. I have this class which, when called, constructs a new window (draw() method). The drawGUI() calls a processing method at the end (compare() method).
basically the structure is
public static void draw() {
// draws stuff
compare();
}
The problem is that the window drawn by drawGUI() has some major visual artifacts till the processing (compare() ) is over.
What is the simplest way I can implement to launch compare() after draw() has finished executing? Thank you
The simplest way is to just put your draw() code inside an asyncExec() inside your thread at the end
new Thread(new Runnable() {
public void run() {
//do long running blocking bg stuff here
Display.getDefault().asyncExec(new Runnable() {
public void run() {
draw();
}
}
}).start();
Assuming that the reason you're getting the artefacts is that draw() hasn't had a chance to return, you can use a Thread.
final T parent = this;
new Thread(new Runnable() {
public void run() {
parent.compare();
}
}).start();
(Where T is the type of the class that has your compare method).

SwingUtilites: how to return values from another thread in Java?

I am trying to make an application in Java.
To make Swing work correctly, I did this:
public static void main(String[] array){
String outerInput;
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
// I want this string input.
String input = JOptionPane.showInputDialog(
null,"Stop ?", JOptionPane.QUESTION_MESSAGE);
});
// How can I get this input value in String outerInput?
}
How would I get this input string in my main body?
You can use AtomicReference<String> for passing values between threads in a thread-safe manner.
As noted by Hemal, you'll need some synchronization between two threads to make sure it was already executed. For example, you can use CountDownLatch or use SwingUtilities.invokeAndWait (make sure you don't call it from Swing thread!)
Update: here is the complete example using AtomicReference and CountDownLatch
public class Main {
public static void main(String[] args) throws InterruptedException {
final AtomicReference<String> result = new AtomicReference<String>();
final CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
String input = JOptionPane.showInputDialog(null, "Stop?", "Stop?", JOptionPane.QUESTION_MESSAGE);
result.set(input);
// Signal main thread that we're done and result is set.
// Note that this doesn't block. We never call blocking methods
// from Swing Thread!
latch.countDown();
}
});
// Here we need to wait until result is set. For demonstration purposes,
// we use latch in this code. Using SwingUtilities.invokeAndWait() would
// be slightly better in this case.
latch.await();
System.out.println(result.get());
}
}
Also read this answer about general design of GUI (and Swing) applications.
How would I get this input string in my main body?
You wouldn't. The idea that your "main" would invoke a Swing dialog box and then do something with the results is contrary to the entire idea of a graphical user interface.
In a GUI, you design your program to deal with a series of user-initiated events. Those events may be completely asynchronous, such as the keystrokes, selection, and menu choices of your typical word processor. Or they may be scripted, such as the question-answer format of a "wizard."
Assuming that you want to do something like the latter, you would implement it using the following sequence:
The user initiates some action, perhaps selecting a menu choice. This is turned into an invocation of an ActionListener, which decides that it needs more input from the user.
The ActionListener, which is executed on the event dispatch thread, is permitted to do anything that it wants to the UI, such as displaying a dialog. That dialog may be modal or non-modal; in one case the output is available to the original listener, in the other you need to write a new listener to take subsequent action.
Once you have enough information, you may choose to invoke a background operation. You would typically have a thread-pool to service these requests. You would not attempt to perform the request on the "main" thread; in fact, for all intents the main thread is no longer running.
When your operation completes running, it would push data back to the event dispatch thread using SwingUtilities.invokeLater(). While you could use invokeAndWait() to send results to Swing in the middle of your background operation, that's rarely a good idea. Instead, create a sequence of operations, preferably one that is easily canceled by the user.
The "standard" way to initiate operations on a background thread is via SwingWorker. There are alternatives; for example, you could use a BlockingQueue to send operations to a single long-running background thread, and use invokeLater() to return the results.
Regardless, there's one rule that you do not want to break: never, ever, perform a blocking operation on the event dispatch thread. If you do that, then your application is broken.
Right now you have two threads going: the main thread and the EDT (event dispatch thread). I assume you know that SwingUtilities.invokeLater(runnable) is running a task on the EDT.
To share data between threads, you just need some variable that is in the scope of both threads. The easiest way to accomplish that is to declare a volatile data member or AtomicReference in the class containing the main method.
In order to ensure that you read the value after it is returned by the JOptionPane, the simplest thing you can do here is to change the invokeLater call to an invokeAndWait call. This will cause your main thread to stop executing until what you have put onto the EDT has completed.
Ex:
public class MyClass {
private static volatile String mySharedData;
public static void main(String[] args) throws InterruptedException {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
mySharedData = JOptionPane.showInputDialog(null, "Stop ?", JOptionPane.QUESTION_MESSAGE);
}
});
// main thread is blocked, waiting for the runnable to complete.
System.out.println(mySharedData);
}
}
If your main thread is executing some task that shouldn't be stopped while the option pane is present, then in the main thread you can periodically check (i.e., in the outer part of the loop that is running your task) whether or not mySharedData has been set. If your task doesn't loop and is instead doing some I/O or waiting, you can make use of Thread.interrupt and check mySharedData in the InterruptedExecption handlers.
I suggest using the observer/observable pattern for this, perhaps with a PropertyChangeListener. Then your Swing app will be able to notify any and all listeners if the critical variable(s) state changes.
For example:
import java.awt.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.event.*;
public class ListenToSwing {
public static final String STATE = "state";
private static final int STATE_MAX = 10;
private static final int STATE_MIN = -10;
private JPanel mainPanel = new JPanel();
private int state = 0;
private JSlider slider = new JSlider(STATE_MIN, STATE_MAX, 0);
public ListenToSwing() {
mainPanel.add(slider);
slider.setPaintLabels(true);
slider.setPaintTicks(true);
slider.setMajorTickSpacing(5);
slider.setMinorTickSpacing(1);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
setState(slider.getValue());
}
});
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
mainPanel.addPropertyChangeListener(listener);
}
public Component getMainPanel() {
return mainPanel;
}
public void setState(int state) {
if (state > STATE_MAX || state < STATE_MIN) {
throw new IllegalArgumentException("state: " + state);
}
int oldState = this.state;
this.state = state;
mainPanel.firePropertyChange(STATE, oldState, this.state);
}
public int getState() {
return state;
}
public static void main(String[] args) {
final ListenToSwing listenToSwing = new ListenToSwing();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("ListenToSwing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(listenToSwing.getMainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
listenToSwing.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(ListenToSwing.STATE)) {
System.out.println("New state: " + listenToSwing.getState());
}
}
});
}
}
You can use an AtomicReference and invokeAndWait.
public static void main(String[] array){
AtomicReference<String> outerInput = new AtomicReference<String>();
SwingUtilities.invokeAndWait(new Runnable(){
#Override
public void run() {
String input = JOptionPane.showInputDialog(
null,"Stop ?", JOptionPane.QUESTION_MESSAGE);
outerInput.set(input);
});
outerInput.get(); //Here input is returned.
}
You can trivially expose it to the outer class by declaring a String[] in which the runnable sets the value. But note that you will need some synchronization mechanism to know whether it has been assigned by the Runnable.
The following code will do what you want. I have done something similar except I was launching a JFileChooser instead of an input dialog. I found it more convenient than hard coding a bunch of paths into my application or accepting a command line argument, at least for testing purposes. I would like to add that one could modify the prompt() method to return the FutureTask instance for added flexibility.
public class Question {
public static void main(String[] args) {
Question question = new Question();
String message = "Stop?";
System.out.println(message);
// blocks until input dialog returns
String answer = question.ask(message);
System.out.println(answer);
}
public Question() {
}
public String ask(String message) {
try {
return new Prompt(message).prompt();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
private class Prompt implements Callable<String> {
private final String message;
public Prompt(String message) {
this.message = message;
}
/**
* This will be called from the Event Dispatch Thread a.k.a. the Swing
* Thread.
*/
#Override
public String call() throws Exception {
return JOptionPane.showInputDialog(message);
}
public String prompt() throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<>(this);
SwingUtilities.invokeLater(task);
return task.get();
}
}
}

Weird. Query works in Fuseki HTML form but not from JAVA?

I would like to ask some questions regarding the use of threads. I have looked at a lot of posts and links suggested from those posts but still came up blank.
I have a NetBeans project that has a few classes. One of them is the Gui class that I use to just click a button and some processing gets performed. From the Gui I call an instance of another class that in turn calls other classes. One of these classes submits a Sparql query to a TDB backend database. All output is saved to files for now.
What I would like to do is to somehow make the class called from the Gui to run on another thread and also to be able to update an EditorPane and a TextArea on the Gui from one or more of the called classes. Up to now I have tried calling an instance of the Gui class and use a public method within but this does not work. I am calling the instance Gui with
Gui gui = new Gui();
gui.setEditorPaneText("File name is: " + fn);
and the method in the Gui class is
public void setEditorPaneText(final String string) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setString(string);
EditorPane.setText(getString());
EditorPane.repaint();
}
});
}
I tried running the debugger but the processing skips from the first line of the method to the last curly bracket without processing the code within. My Gui class has the following as a main method. The commented part was a previous version of the event queue that I changed while I was reading through the numerous posts on the issue.
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Gui().setVisible(true);
throw new UnsupportedOperationException("Not supported yet.");
}
});
}
The following is the previous code of the main method that I replaced after reading some of the posts on this issue.
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Gui().setVisible(true);
}
});
Any helpful information will be much appreciated. Thank you.
I think your main error is that you create two instances of your Gui class. You have the following snippet twice: new Gui(). Take a look at my example code below to see an example how to pass the Gui to your worker thread.
// This is handwritte-untested-uncompiled code to show you what I mean
public class Main {
public static void main(String[]args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Gui g = new Gui();
g.show(); // show() is equal to setVisible(true)
g.doBackendAction(); // Normally this would be invoked by a button or sthg. I was to lazy
}
});
}
}
public class Gui extends JFrame {
private JTextArea area;
public Gui() {
// Just some code to create the UI. Not sure if this actually does sthg right :P
area = new JTextArea();
setContentPane(area);
pack();
}
public void setTextAreaContent(final String string) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
area.setText(string);
this.repaint(); // Not 100% sure if we need this
}
});
}
public void doBackgroundWork() {
BackgroundWorker w = new BackgroundWorker(this);
new Thread(w).start(); // Start a worker thread
}
}
public class BackgroundWorker implements Runnable {
private Gui gui;
public BackgroundWorker(Gui gui) {
this.gui = gui; // we take the initial instance of Gui here as a parameter and store it for later
}
public void run() {
try { Thread.sleep(10 * 1000); } catch (InterruptedException e) {; }
this.gui.setTextAreaContent("Hello World!"); // calls back to the Gui to set the content
}
}

Categories