Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass results from EDT back to a different thread?

I have the following use-case:

I have code executing in Thread A (not EDT). Then I want to ask the user a question, but this must be done on the EDT as it involves Swing code (opening a dialog etc). Finally, I want to pass the user's answer back to Thread A, so it can continue.

I'm struggling to find a good way to pass the user's answer back to Thread A. How do you do this?

like image 457
Eric Lindauer Avatar asked Nov 20 '12 03:11

Eric Lindauer


4 Answers

FutureTask<Integer> dialogTask = new FutureTask<Integer>(new Callable<Integer>() {
  @Override public Integer call() {
    return JOptionPane.showConfirmDialog(...);
  }
});
SwingUtilities.invokeLater(dialogTask);
int result = dialogTask.get();
like image 80
jtahlborn Avatar answered Nov 09 '22 08:11

jtahlborn


From within thread A, you can use SwingUtilities.invokeAndWait(Runnable) to execute your user prompt on the EDT. This will block thread A until your runnable completes (i.e., until the user has submitted a result and you have stored it somewhere). Your runnable can be written to store the result somewhere that thread A can access it, once thread A regains control.

like image 27
JimN Avatar answered Nov 09 '22 08:11

JimN


Basically, you need to use EventQueue#invokeAndWait (AKA SwingUtilities#invokeAndWait). This will block the current thread until the run method returns.

The real trick is trying to get it setup so that you can get the return value ;)

public class TestOptionPane03 {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                String choice = ask("Chocolate", "Strewberry", "Vanilla");
                System.out.println("You choose " + choice);
            }
        }).start();
    }

    public static String ask(final String... values) {

        String result = null;

        if (EventQueue.isDispatchThread()) {

            JPanel panel = new JPanel();
            panel.add(new JLabel("Please make a selection:"));
            DefaultComboBoxModel model = new DefaultComboBoxModel();
            for (String value : values) {
                model.addElement(value);
            }
            JComboBox comboBox = new JComboBox(model);
            panel.add(comboBox);

            int iResult = JOptionPane.showConfirmDialog(null, panel, "Flavor", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
            switch (iResult) {
                case JOptionPane.OK_OPTION:
                    result = (String) comboBox.getSelectedItem();
                    break;
            }

        } else {

            Response response = new Response(values);
            try {
                SwingUtilities.invokeAndWait(response);
                result = response.getResponse();
            } catch (InterruptedException | InvocationTargetException ex) {
                ex.printStackTrace();
            }

        }

        return result;

    }

    public static class Response implements Runnable {

        private String[] values;
        private String response;

        public Response(String... values) {
            this.values = values;
        }

        @Override
        public void run() {
            response = ask(values);
        }

        public String getResponse() {
            return response;
        }
    }
}

In this example, I basically create my own query object the implements Runnable and can store the response from the user

like image 2
MadProgrammer Avatar answered Nov 09 '22 07:11

MadProgrammer


I wrote the following convenience method to add to jtahlborn's answer. It adds a check to avoid blocking the EDT, and provides a nice stream-lined exception handling:

/**
 * executes the given callable on the EDT, blocking and returning the result of the callable.call() method.
 * 
 * If call() throws an exception, it is rethrown on the the current thread if the exception is either a RuntimeException, or the
 * class that is assignable to exceptionClass. Otherwise, it is wrapped in a RuntimeException and thrown on the current thread.
 * 
 * @param exceptionClass The class of any exception that may be thrown by the callable.call() method, which will now be thrown
 *            directly by this method (ie, not wrapped in an ExecutionException)
 */
public static <T, E extends Exception> T invokeAndWaitAndReturn(Callable<T> callable, Class<E> exceptionClass)
        throws InterruptedException, E {
    if (SwingUtilities.isEventDispatchThread()) {
        try {
            return callable.call();
        }
        catch (Exception e) {
            throw throwException(exceptionClass, e);
        }
    }
    else {
        FutureTask<T> task = new FutureTask<T>(callable);
        SwingUtilities.invokeLater(task);

        try {
            return task.get();
        }
        catch (ExecutionException ee) {
            throw throwException(exceptionClass, ee.getCause());
        }
    }
}

@SuppressWarnings("unchecked")
private static <E extends Exception> E throwException(Class<E> exceptionClass, Throwable t) {
    if (exceptionClass.isAssignableFrom(t.getClass())) {
        return (E) t;
    }
    else if (t instanceof RuntimeException) {
        throw (RuntimeException) t;
    }
    else {
        throw new RuntimeException(t);
    }
}

You call it like this, and don't need to worry whether you are currently executing on the EDT or not:

try {
    Integer result = invokeAndWaitAndReturn(new Callable<Integer>() {
        public Integer call() throws MyException {
            // do EDT stuff here to produce the result
        }
    }, MyException.class);
} catch(InterruptedException ie) {
    Thread.currentThread().interrupt();
} catch(MyException me) {
    // handle the "expected" Exception here
}
like image 2
Eric Lindauer Avatar answered Nov 09 '22 09:11

Eric Lindauer