SwingUtilites: как вернуть значения из другого потока в Java? - PullRequest
6 голосов
/ 14 сентября 2011

Я пытаюсь сделать приложение на Java.Чтобы заставить Swing работать правильно, я сделал это:

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

Как бы я получил эту строку ввода в моем основном теле?

Ответы [ 7 ]

5 голосов
/ 14 сентября 2011

Как бы я получил эту входную строку в моем основном теле?

Вы бы не стали.Идея о том, что ваш «main» будет вызывать диалоговое окно Swing и затем что-то делать с результатами, противоречит самой идее графического пользовательского интерфейса.

В графическом интерфейсе вы разрабатываете свою программу для работы ссерия пользовательских событий.Эти события могут быть полностью асинхронными, такими как нажатия клавиш, выбор и выбор меню вашего типичного текстового процессора.Или они могут быть написаны по сценарию, например, в формате «вопрос-ответ» «мастера».

Предполагая, что вы хотите сделать что-то подобное последнему, вы бы реализовали это, используя следующую последовательность:

  1. Пользователь инициирует какое-то действие, возможно, выбирая пункт меню.Это превращается в вызов ActionListener, который решает, что ему нужно больше ввода от пользователя.
  2. ActionListener, который выполняется в потоке диспетчеризации событий, разрешено делать все, что онхочет пользовательский интерфейс, например, отображение диалога.Этот диалог может быть модальным или немодальным;в одном случае выходные данные доступны исходному слушателю, а в другом - вам нужно написать нового слушателя, чтобы выполнить последующее действие.
  3. Как только у вас будет достаточно информации, вы можете вызвать фоновую операцию.Обычно у вас есть пул потоков для обслуживания этих запросов.Вы не пытаетесь выполнить запрос в «главном» потоке;на самом деле, несмотря ни на что, основной поток больше не работает.
  4. Когда ваша операция завершится, она отправит данные обратно в поток диспетчеризации событий, используя SwingUtilities.invokeLater().Хотя вы можете использовать invokeAndWait() для отправки результатов в Swing во время фоновой операции, это редко хорошая идея.Вместо этого создайте последовательность операций, предпочтительно ту, которая легко отменяется пользователем.

«Стандартный» способ инициировать операции в фоновом потоке - через SwingWorker .Есть альтернативы;например, вы можете использовать BlockingQueue для отправки операций в один длительный фоновый поток и использовать invokeLater() для возврата результатов.

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

5 голосов
/ 14 сентября 2011

Вы можете использовать AtomicReference<String> для передачи значений между потоками потокобезопасным способом.

Как отметил Хемаль, вам потребуется некоторая синхронизация между двумя потоками, чтобы убедиться, что она уже выполнена. Например, вы можете использовать CountDownLatch или SwingUtilities.invokeAndWait (убедитесь, что вы не вызываете его из потока Swing!)

Обновление: вот полный пример с использованием AtomicReference и 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());
    }
}

Также прочитайте этот ответ об общем дизайне приложений GUI (и Swing).

2 голосов
/ 14 сентября 2011

Прямо сейчас у вас есть два потока: основной поток и EDT (поток отправки событий).Я предполагаю, что вы знаете, что SwingUtilities.invokeLater(runnable) выполняет задачу в EDT.

Чтобы обмениваться данными между потоками, вам просто нужна переменная, которая находится в области действия обоих потоков.Самый простой способ сделать это - объявить volatile элемент данных или AtomicReference в классе, содержащем метод main.

Для того, чтобы прочитал значение после itвозвращается JOptionPane, самое простое, что вы можете здесь сделать, - это изменить вызов invokeLater на вызов invokeAndWait.Это приведет к тому, что ваш основной поток прекратит выполнение, пока не завершится то, что вы положили в EDT.

Пример:

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

Если ваш основной поток выполняет какую-то задачу, которая не должнаостановлен, пока присутствует панель параметров, затем в главном потоке вы можете периодически проверять (т. е. во внешней части цикла, в котором выполняется ваша задача), был ли установлен mySharedData.Если ваша задача не зациклена и вместо этого выполняет какой-либо ввод-вывод или ожидание, вы можете использовать Thread.interrupt и проверить mySharedData в обработчиках InterruptedExecption.

1 голос
/ 16 сентября 2014

Вы можете использовать AtomicReference и 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 голос
/ 14 сентября 2011

Я предлагаю использовать для этого шаблон наблюдателя / наблюдаемого, возможно, с PropertyChangeListener. Тогда ваше приложение Swing сможет уведомить всех слушателей об изменении состояния критической (ых) переменной (ей).

Например:

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

}
0 голосов
/ 31 марта 2014

Следующий код сделает то, что вы хотите. Я сделал нечто подобное, за исключением того, что я запускал JFileChooser вместо диалогового окна ввода. Я нашел это более удобным, чем жесткое кодирование множества путей в моём приложении или принятие аргумента командной строки, по крайней мере, для целей тестирования. Я хотел бы добавить, что можно изменить метод prompt(), чтобы он возвращал экземпляр FutureTask для дополнительной гибкости.

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

    }

}
0 голосов
/ 14 сентября 2011

Вы можете тривиально представить его внешнему классу, объявив String[], в котором runnable устанавливает значение.Но учтите, что вам понадобится некоторый механизм синхронизации, чтобы узнать, был ли он назначен Runnable.

...