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

7

6

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?

Humfrid answered 14/9, 2011 at 15:24 Comment(2)
If you want to have result (return value) of the SwingUtilities thread execution, use invokeAndWait instead of invokeLater with any final reference object (which has a another object/primitive to store the result) passing into run().Urbanity
Have a look at stackoverflow.com/a/13466330 if the task run by the EDT can throw an (unchecked) exception and you want to handle it in the non-EDT.Trela
H
5

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Hippocrates answered 14/9, 2011 at 19:33 Comment(2)
@Hippocrates Does this means that in ActionListener i would not have to use SwingUtilities ?Humfrid
@ASHISH - I suggest that you work through the Swing tutorial: download.oracle.com/javase/tutorial/uiswing paying particular attention to the section "Concurrency in Swing."Hippocrates
C
6

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.

Cecilycecity answered 14/9, 2011 at 15:41 Comment(8)
I have upvoted both the answer and the comment, the answer because I did not know about invokeAndWait and the comment for pointing the locking out UI.Assemblage
@parsifal: once you have two threads exchanging data, you need to coordinate them somehow (unless you want to use polling in one thread to check if value has been written). As with all multithreading code, it's prone to errors. Swing adds some small complexity with its EDT, but it's no different than other multithreaded code. How else would you solve it?Eurydice
@Peter - a well-written Swing application uses the UI to initiate operations on a background thread, which may then send their results back to the event dispatch thread. One way to achieve this is with SwingWorker.Hippocrates
@parsifal: sure, but SwingWorker doesn't really fit into this specific case without rewriting applicationEurydice
@Peter: it's hard to tell what the OP's specific case is however without more information. A SwingWorker is both a Future and so returns a value and also has its own Property Change Support, giving it many ways to communicate with the EDT and other threads. A CountDownLatch is one way to listen for a thread to finish but it has the disadvantage of blocking at the await method.Thury
@Hovercraft, on the other hand, swing listeners are still executed on EDT, so they are poor replacement for inter-thread communication. Anyway, maybe you're right, and they would be better fit, but we can't know without more details. I was thinking more about how to fill in 'template' in the question than what would be the best solution for real problem that questioner has.Eurydice
@Peter - After looking at your edits, I still think it's the wrong approach, but it's not dangerous, so I deleted my earlier comment.Hippocrates
@parsifal, I agree with you that approach described in your answer is better in general.Eurydice
H
5

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Hippocrates answered 14/9, 2011 at 19:33 Comment(2)
@Hippocrates Does this means that in ActionListener i would not have to use SwingUtilities ?Humfrid
@ASHISH - I suggest that you work through the Swing tutorial: download.oracle.com/javase/tutorial/uiswing paying particular attention to the section "Concurrency in Swing."Hippocrates
C
3

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.

Chavez answered 14/9, 2011 at 15:42 Comment(4)
Except that mySharedData has to be staticAssemblage
@Hovercraft mlaw's original answer had mySharedData as an instance variable. It has been edited now.Assemblage
@Hovercraft He was just making a comment for this narrow example... there was an error in the original posting.Chavez
@Matt: thanks for the clarification. I withdraw my earlier concern then and will delete my comment.Thury
T
1

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

}
Thury answered 14/9, 2011 at 16:56 Comment(2)
Edited to take advantage of property change support inherent in all swing components.Thury
@downvoter: I don't mind down-votes, just please tell me your objection in a comment so we can discuss it, and so I can learn from it.Thury
H
1

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.
}
Hewett answered 16/9, 2014 at 13:47 Comment(0)
A
0

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.

Assemblage answered 14/9, 2011 at 15:31 Comment(2)
Synchronization to know whether it has been assigned? Sorry, but that doesn't make sense to me.Thury
@Hovercraft yes. When one thread assigns a variable and another thread uses it some synchronization mechanism is required.Assemblage
B
0

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

    }

}
Bibbs answered 30/3, 2014 at 20:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.