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

I am trying to make a Java application. To get Swing to work correctly, I did the following:

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 do I get this input line in my main body?

+6
source share
7 answers

How do I get this input line in my main body?

You wouldn’t. The idea that your “main” will invoke the Swing dialog box and then do something with the results contradicts the whole idea of ​​a graphical user interface.

In the graphical interface, you develop your program for processing a series of user-initiated events. These events can be completely asynchronous, such as keystrokes, menu selection and selection of your typical word processor. Or they can be written in a script, for example, in a question-answer format of a "wizard".

Assuming you want to do something like the latter, you implement it using the following sequence:

  • The user initiates an action, possibly by selecting a menu item. This turns into an ActionListener call, which decides that it needs more input from the user.
  • ActionListener that runs in the event dispatch thread is allowed to do whatever it wants for the user interface, for example, display a dialog. This dialogue may be modal or non-modal; in one case, the output is available to the original listener, and in the other you must write a new listener for the next action.
  • After you have enough information, you can choose to call the background operation. Usually you should have a thread pool to serve these requests. You will not try to fulfill the request for the "main" thread; in fact, for all purposes, the main thread no longer works.
  • When your operation completes, it will return the data back to the event dispatch stream using SwingUtilities.invokeLater() . Although you can use invokeAndWait() to send Swing results in the middle of your background operation, this is 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 in the background thread is through SwingWorker . There are alternatives; for example, you can use BlockingQueue to send operations to one long background thread and use invokeLater() to return the results.

Regardless, there is one rule that you do not want to break: never perform a blocking operation in the event dispatch thread . If you do, your application will be broken.

+5
source

You can use AtomicReference<String> to pass values ​​between threads in a thread-safe way.

As noted by Hemal, you will need synchronization between the two threads to make sure that it is already done. For example, you can use CountDownLatch or use SwingUtilities.invokeAndWait (make sure you don't call it from the Swing thread!)

Update: here is a 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 the general design of GUI applications (and Swing).

+5
source

Now you have two streams: main stream and EDT (event sending stream). I suppose you know that SwingUtilities.invokeLater(runnable) runs the task on EDT.

To exchange data between threads, you just need a variable that is included in the volume of both threads. The easiest way to achieve this is to declare a volatile or AtomicReference data element in a class containing the main method.

To make sure you read the value after it returns, JOptionPane , the easiest thing you can do here is to change the invokeLater call to invokeAndWait . This will cause the main thread to stop executing until you finish what you put on the EDT.

Example:

 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 performs some task that should not stop while the options panel is present, then you can periodically check in the main thread (that is, in the outer part of the loop in which your task is executed), regardless of whether or not mySharedData . If your task does not loop and instead performs some I / O or wait operations, you can use Thread.interrupt and check mySharedData in InterruptedExecption handlers.

+2
source

I suggest using an observable / observable pattern for this, perhaps with a PropertyChangeListener. Then your Swing application will be able to notify everyone and all listeners if the state of a critical variable changes.

For instance:

 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()); } } }); } } 
+1
source

You can use 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. } 
+1
source

You can trivially set it to an outer class by declaring a String[] , in which runnable sets the value. But note that you will need some kind of synchronization mechanism to find out if it has been assigned Runnable .

0
source

The following code will do what you want. I did something similar, except that instead of the input dialog, I ran JFileChooser . I found this more convenient than hard coding a bunch of paths in my application or accepting a command line argument, at least for testing purposes. I would like to add that you can modify the prompt() method to return an instance of FutureTask 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 aka 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(); } } } 
0
source

Source: https://habr.com/ru/post/897325/


All Articles