Возврат значений из Swing с использованием invokeAndWait

Я использовал следующий подход для создания компонентов и возврата значений из Swing в / из-за пределов EDT. Например, следующий метод может быть расширением JFrame, чтобы создать JPanel и добавить его к родителю 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];
}

Локальный массив 1 длины используется для передачи "результата" изнутри Runnable, который вызывается в EDT. Ну, это выглядит "немного" хаки, и поэтому мои вопросы:

  1. Имеет ли это смысл? Кто-нибудь еще делает что-то подобное?
  2. Является ли массив длины 1 хорошим способом передачи результата?
  3. Есть ли более простой способ сделать это?

4 ответа

Решение
  • Глотать исключения, даже не регистрируя их: плохо! - вы будете ненавидеть себя, когда столкнетесь с чем-то подобным после двухчасовой охоты на насекомых
  • Нет, массив не очень хороший способ; с одной стороны, он не предлагает простому методу для вызывающего кода ждать, пока поток EDT выполнит Runnable до получения результата
  • Есть класс, разработанный специально для такого рода вещей: SwingWorker

Хотя этот метод может иметь смысл в некоторых ситуациях, он будет бесполезен большую часть времени.

Причина в том, что создание большинства (если не всех) ваших компонентов всегда будет происходить из EDT, в результате действий пользователя (пункт меню или нажатие кнопки), которые всегда выполняются из EDT.

В тех случаях, когда у вас есть большая работа, которую нужно выполнить перед созданием панели, и вы не хотите блокировать EDT, вам следует, как это было предложено кем-то другим, использовать SwingWorker или среду Swing, которая предлагает поддержку для длинных задач (обычно основанных на SwingWorker внутри все равно, но не обязательно).

Что касается вашего вопроса 2, к сожалению, у вас не так много способов сделать это:

  • Используйте массив из 1 элемента, как вы это сделали, это самое простое, но и самое уродливое решение
  • Создайте класс ItemHolder (см. Ниже), который делает почти то же самое, требует немного больше работы и, на мой взгляд, чище
  • Наконец, используйте возможности java.util.concurrent (Future и Callable); Я думаю, что это было бы самым бесспорным делом, но это также требует больших усилий

Вот упрощенный класс ItemHolder:

public class ItemHolder<T> {
    public void set(T item) {...}
    public T get() {...}
    private T item;
}
  1. а) Это имеет смысл. б) не то, что я знаю.
  2. Так хорошо, как и любой.
  3. Создайте JPanel вне invokeAndWait вызов

// Эта строка добавлена, чтобы успокоить уценку

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

Вы можете легко проверить, является ли текущий поток EDT, а затем выполнить правильно и проще в этом контексте. Что касается использования окончательного массива для получения возвращаемого значения, это самый простой способ, когда вам нужно использовать анонимный внутренний класс, подобный этому.

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];
    }
}
Другие вопросы по тегам