SwingUtilites:如何从Java中的另一个线程返回值?

时间:2011-09-14 15:24:49

标签: java multithreading swing input swingutilities

我正在尝试用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 个答案:

答案 0 :(得分:5)

您可以使用AtomicReference<String>以线程安全的方式在线程之间传递值。

如Hemal所述,您需要在两个线程之间进行一些同步,以确保它已经执行。例如,您可以使用CountDownLatch或使用SwingUtilities.invokeAndWait(确保您不要从Swing线程调用它!)

更新:以下是使用AtomicReferenceCountDownLatch

的完整示例
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());
    }
}

另请阅读this answer关于GUI(和Swing)应用程序的一般设计。

答案 1 :(得分:5)

  

如何在我的主体中获取此输入字符串?

你不会。你的“main”调用Swing对话框然后对结果做一些事情的想法与图形用户界面的整个想法相反。

在GUI中,您可以设计程序来处理一系列用户启动的事件。这些事件可能是完全异步的,例如典型文字处理器的击键,选择和菜单选择。或者它们可能是脚本化的,例如“向导”的问答形式。

假设您想要执行后者之类的操作,您可以使用以下序列实现它:

  1. 用户启动一些操作,可能选择菜单选项。这变成了ActionListener的调用,它决定了它需要来自用户的更多输入。
  2. 允许在事件发送线程上执行的ActionListener对UI执行任何操作,例如显示对话框。该对话可以是模态的或非模态的;在一种情况下,输出可供原始侦听器使用,另一种情况下,您需要编写新的侦听器以执行后续操作。
  3. 获得足够的信息后,您可以选择调用后台操作。您通常会有一个线程池来为这些请求提供服务。您不会尝试在“主”线程上执行请求;事实上,对于所有意图,主线程不再运行。
  4. 当您的操作完成运行时,它会使用SwingUtilities.invokeLater()将数据推送回事件派发线程。虽然您可以使用invokeAndWait()在后​​台操作过程中将结果发送到Swing,但这不是一个好主意。相反,创建一系列操作,最好是一个易于被用户取消的操作。
  5. 在后台线程上启动操作的“标准”方法是SwingWorker。还有其他选择;例如,您可以使用BlockingQueue将操作发送到单个长时间运行的后台线程,并使用invokeLater()返回结果。

    无论如何,您想要破解的规则是:永远不会对事件派发线程执行阻止操作。如果您这样做,那么您的应用程序已损坏

答案 2 :(得分:2)

现在你有两个主题:主线程和EDT(事件调度线程)。我假设您知道SwingUtilities.invokeLater(runnable)正在EDT上运行任务。

要在线程之间共享数据,您只需要一些在两个线程范围内的变量。最简单的方法是在包含main方法的类中声明volatile数据成员或AtomicReference

为了确保读取之后的值JOptionPane,您可以在这里做的最简单的事情是将invokeLater调用更改为{{1}打电话。这将导致主线程停止执行,直到您放入EDT的内容完成为止。

例如:

invokeAndWait

如果您的主线程正在执行某个在选项窗格存在时不应该停止的任务,那么在主线程中您可以定期检查(即,在运行您的任务的循环的外部)是否是否已设定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); } } 。如果您的任务没有循环,而是进行一些I / O或等待,您可以使用Thread.interrupt并检查InterruptedExecption处理程序中的mySharedData

答案 3 :(得分:1)

我建议使用观察者/可观察模式,也许使用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());
            }
         }
      });
   }

}

答案 4 :(得分:1)

您可以使用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.
}

答案 5 :(得分:0)

通过声明runnable设置值的String[],可以将它暴露给外部类。但请注意,您需要一些同步机制来了解它是否已由Runnable分配。

答案 6 :(得分:0)

以下代码将执行您想要的操作。除了我正在启动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();
        }

    }

}