views:

126

answers:

4

I've been using the following approach to create components and return values from Swing to/from outside the EDT. For instance, the following method could be an extension to JFrame, to create a JPanel and add it to the parent JFrame:

public JPanel threadSafeAddPanel() {

    final JPanel[] jPanel = new JPanel[1];

    try {
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                jPanel[0] = new JPanel();
                add(jPanel[0]);
            }
        });
    } catch (InterruptedException ex) {
    } catch (InvocationTargetException ex) {
    }

    return jPanel[0];
}

The local 1-length array is used to transfer the "result" from inside the Runnable, which is invoked in the EDT. Well, it looks "a bit" hacky, and so my questions:

  1. Does this make sense? Is anybody else doing something like this?
  2. Is the 1-length array a good way of transferring the result?
  3. Is there an easier way to do this?
A: 
  1. a) It makes sense. b)not that I know of.
  2. As good as any.
  3. Create the JPanel outside of the invokeAndWait call

//This line added to appease markdown

public JPanel threadSafeAddPanel() {
    final JPanel jPanel = new JPanel();
    try {
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                add(jPanel);
            }
        });
    } catch (InterruptedException ex) {
    } catch (InvocationTargetException ex) {
    }
    return jPanel;
}
KitsuneYMG
While 3. most likely works, it's not strictly speaking allowed.
Joonas Pulakka
I agree with Joonas, creating Swing components outside the EDT creates a bad precedent and should be avoided.
Nemi
+1  A: 
  • Swallowing exceptions without even logging them: bad!! - you will hate yourself when you come upon something like that after a 2 hours bug-hunt
  • No, the array is not a good way; for one thing, it offers no easy method for the calling code to wait for the EDT thread to execute the Runnable before fetching the result
  • There's a class designed explicitly for this kind of thing: SwingWorker
Michael Borgwardt
1. You're right :-) 2. Ok, but invokeAndWait does that. 3. Perhaps I should be using it, although for simple "getters" it feels a bit overkill.
Joonas Pulakka
2. Oh, right, that somehow didn't register.
Michael Borgwardt
+1  A: 

Although that method may make sense in some situations, it will be useless most of the time.

The reason is that the creation of most (if not all) of your components will always occur from the EDT, as a result of a user action (menu item or button clicked) which are always executed from the EDT.

In cases where you have big work to perform before creating your panel and you don't want to block the EDT, then you should, as suggested by someone else, use SwingWorker or a Swing framework that offer support for long tasks (generally based on SwingWorker internally anyway, but not necessarily).

Regarding your question 2, unfortunately you don't have many ways to do that:

  • Use a 1-item array as you did, that's the easiest but also ugliest solution
  • Create a ItemHolder class (see below) that does almost the same, requires a bit more work and is cleaner, in my opinion
  • Last, use java.util.concurrent facilities (Future and Callable); that would be the cleaniest I think, but also it requires the most effort

Here is, simplified, the ItemHolder class:

public class ItemHolder<T> {
    public void set(T item) {...}
    public T get() {...}
    private T item;
}
jfpoilpret
+1  A: 

You can easily check to see if the current thread is the EDT and then execute correctly and more simply in that context. As for using the final array for getting the return value, that is the easiest way when you have to use an anonymous inner class like this.

public JPanel threadSafeAddPanel() throws InterruptedException, 
        InvocationTargetException {
    if (EventQueue.isDispatchThread()) {
        JPanel panel = new JPanel();
        add(panel);

        return panel; 
    } else {
        final JPanel[] jPanel = new JPanel[1];
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                jPanel[0] = new JPanel();
                add(jPanel[0]);
            }
        });

        return jPanel[0];
    }
}
Kevin Brock