【问题标题】:Updating GUI from a runnable从可运行文件更新 GUI
【发布时间】:2012-04-28 16:29:23
【问题描述】:

我正在构建一个 Swing 应用程序,其中一部分功能应该是处理和输出一些视觉和听觉的文本(使用 Mary TTS)。我需要一些关于 GUI 和文本处理类进行通信的最佳方式的建议。

GUI 类是 JPanel 的子类。其中我有一个实现 Runnable 的类,称为 LineProcesser,它准备发送到音频播放器的文本。我正在使用一个线程执行器来阻止这个 EDT(这可能不是最好的方法,但它似乎达到了我想要的结果)。

我的意图是让 LineProcessor 遍历所有文本并在每行的末尾更新一个 JTextArea。此外,它需要在某些点停止并等待用户输入。用户输入完成后,GUI 类应该告诉它继续处理。

以下代码说明了我目前拥有的内容:

public class MyPanel extends JPanel {
    ExecutorService lineExecutor = Executors.newSingleThreadExecutor();
    Runnable lineProcessor = new LineProcessor();

    public class LineProcessor implements Runnable {

        private int currentLineNo = 0;

            public LineProcessor() {
            //  ...
            }

            @Override
            public void run() {
                // call getText();  
                // call playAudio();
                currentLineNo++;
            }
        }
    }

    private JButton statusLbl = new JLabel();       
    private JButton mainControlBtn = new JButton();

    private void mainControlBtnActionPerformed(ActionEvent evt) {

        if (mainControlBtn.getText().equals("Start")) {
                          lineExecutor.submit(lineProcessor);
                          mainControlBtn.setText("Running");
        }
    }
}

LineProcessor 如何通知 GUI 组件它们需要更改以及如何从 GUI 中暂停和重新启动它?我对是否需要 Swing Worker、属性/事件侦听器或其他东西感到困惑?我读过的例子有点道理,但我看不出如何将它们应用到我这里的代码中。

【问题讨论】:

    标签: java swing user-interface concurrency event-dispatch-thread


    【解决方案1】:

    您需要做的就是将任何 Swing 调用包装在一个 Runnable 中,并通过 SwingUtilities.invokeLater(myRunnable); 在 EDT 上对其进行排队。就是这样。不需要 SwingWorker。

    例如,

    public class LineProcessor implements Runnable {
      private int currentLineNo = 0;
      Runnable LineProcessor = new LineProcessor();  // won't this cause infinite recursion?
    
      public LineProcessor() {
         // ...
      }
    
      @Override
      public void run() {
         // call getText();
         // call playAudio();
         currentLineNo++;
    
         SwingUtilities.invokeLater(new Runnable() {
            public void run() {
               // *** Swing code can go here ***
            }
         });
      }
    }
    

    【讨论】:

    • +1 好建议。 invokeLater 非常适合进度报告,但他需要 invokeAndWait 调用“暂停并等待用户输入”部分
    • aaaaach 谢谢你的回答,因为这里的所有答案(过去两个月)都建议使用(我的观点)非常复杂的 SwingWorker 解决方法,与简单的 Runnable#Thread 和普通的 invokeLater 相比,+1
    • 我的回答不如 robin 或 mprabhat 的好,尽管它们都更全面,并解决了 OP 问题的其他方面。我想我会在几分钟内删除这个答案。
    • @Hovercraft Full Of Eels 请不要,不要删除这个答案,然后我一个人呆在这里,喜欢 Runnable#Thread,或者说我错了......
    • 这种方法非常适合暂停/恢复功能,我喜欢它的简单性。在我的设计中,我不得不使用 CountDownLatch 来避免阻塞 EDT。
    【解决方案2】:

    您必须同时使用 SwingWorker 和 Event 方法。

    1. 将长时间运行的代码放入 Swing Worker。
    2. 创建新的属性更改事件、监听器、管理器
    3. 在您的 SwingWorker 中,当更改事件发生时,调用 PropertyChangeManager 以通知所有 lisener。
    4. 所有希望收到事件通知的 GUI 组件都应向 PropertyChangeManager 注册。
    5. 您的 PropertyChangeManager 将调用 PropertyChangeListener 的 customProperyChange 方法并传递 propertyChangeEvent

    【讨论】:

    • 比我的更简洁的 OOP 方法。 1+
    • @HovercraftFullOfEels 但可能会变得复杂...... UI 组件可能不会在非 EDT 上收到通知(或至少不处理事件),而我可以假设其他方有兴趣拥有线程上的那些事件是否实际发生。对于这种情况,我更喜欢您的简单方法
    • PropertyChangeListener + SwingWorker +1
    【解决方案3】:

    您正在寻找的是SwingWorker。此类允许在工作线程上执行工作,定期更新 EDT,并最终更新 EDT。

    在 SO 和 Swing 教程中提供了几个示例。几个链接

    可以使用publish 方法报告进度,这些结果将传递给您可以更新UI 的process 方法。最后,调用done 方法允许您执行一些最终的 UI 更新。

    对于暂停/重启功能...您可以在 doInBackground 方法中使用 invokeAndWait 和阻塞方法调用(例如显示 JOptionPane 要求用户输入)。但是,如果您开始在doInBackground 中使用invokeAndWait,则使用SwingWorker 可能有点过头了,您可以简单地选择@Hovercraft Full Of Eels 建议的方法

    【讨论】:

    • 谢谢,我现在在代码的其他地方使用 SwingWorker。关于我的问题,我实际上使用了CountDownLatch 而不是invokeAndWait,因为线程正在将控制权返回给主 GUI,我不想在 EDT 上进行阻塞调用。
    • 我也应该说,很好的答案!
    猜你喜欢
    • 2015-01-01
    • 2014-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-29
    相关资源
    最近更新 更多