【问题标题】:Operation priority in Java. (An object instantiates and runs before the GUI is updated?)Java中的操作优先级。 (一个对象在 GUI 更新之前实例化并运行?)
【发布时间】:2011-10-12 00:35:00
【问题描述】:

我希望 GUI 在对象被实例化并实际完成工作之前将按钮的标题从“Go”更改为“Working...”。完成后,我希望按钮的标题切换回“开始”。

代码如下:

    private class convert implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        JButton button = (JButton)e.getSource();

        button.setText("Working...");
        button.setEnabled(false);

        anObject name = new AnObject();
        boolean result = name.methodName(chooser.getSelectedFile(),encoding);

        // A bunch of stuff was here but irrelevant to the question,
        // so it was removed to save room.

        button.setEnabled(true);
        button.setText("Go");
    }

在实践中实际发生的是 name 被实例化,methodName 被调用,然后按钮在屏幕上被更新,尽管我已经告诉了虚拟机首先更改按钮标题。

我的工作理论是,鉴于我没有使这个程序线程化,这与操作优先级或 JVM 的内部线程或其他东西有关......

有什么建议吗?

【问题讨论】:

  • 另一种可能的解决方案是,如果您的“一堆无关紧要的东西......”涉及显示另一个窗口并且您正在使用 JFrame,请改用模态 JDialog。
  • 另请参阅我发布的 SwingWorker 和 AbstractAction 解决方案。

标签: java multithreading swing user-interface


【解决方案1】:

我知道您已经接受了一个解决方案,但是由于您遇到了“冻结 gui”综合症,您肯定遇到了线程问题,并且 invokeLater 不会解决您的问题。正如上面提到的 extraneon,你需要一个 SwingWorker 或一些后台线程来解决这个问题。另外,我认为这是使用 AbstractAction 而不是 ActionListener 的好案例。例如:

import java.awt.Dimension;
import java.awt.event.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class Convert extends AbstractAction {
   private static final long SLEEP_TIME = 3000; // 3 seconds
   private String enabledText;
   private String disabledText;

   public Convert(String enabledText, String disabledText) {
      super(enabledText);
      this.enabledText = enabledText;
      this.disabledText = disabledText;
   }

   public void actionPerformed(ActionEvent e) {
      Object source = e.getSource();
      if (!(source instanceof JButton)) {
         return;
      }
      final JButton button = (JButton) source;
      setButtonEnabled(button, false);
      new SwingWorker<Void, Void>() {
         @Override
         protected Void doInBackground() throws Exception {
            // TODO: long-running code goes here. 
            // Emulated by Thread.sleep(...) 
            Thread.sleep(SLEEP_TIME);
            return null;
         }

         @Override
         protected void done() {
            setButtonEnabled(button, true);
         }
      }.execute();
   }

   public void setButtonEnabled(JButton button, boolean enabled) {
      if (enabled) {
         button.setText(enabledText);
         button.setEnabled(true);
      } else {
         button.setText(disabledText);
         button.setEnabled(false);
      }
   }

   private static void createAndShowUI() {
      JFrame frame = new JFrame("Convert");
      frame.getContentPane().add(new ConvertGui());
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}

@SuppressWarnings("serial")
class ConvertGui extends JPanel {
   public ConvertGui() {
      add(new JButton(new Convert("GO", "Working...")));
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(300, 200);
   }
}

【讨论】:

  • +1,是的,是的,是的!我不明白为什么 OP 认为接受的答案是正确的!不是!
  • 如果从事件分派线程调用​​ invokeLater ——例如,从 JButton 的 ActionListener —— doRun.run() 仍将被推迟,直到所有未决事件都已处理完毕。请注意,如果 doRun.run() 抛出未捕获的异常,则事件调度线程将展开(而不是当前线程)。
  • 我接受了这个答案,因为当我实施该解决方案时,它奏效了。也就是说,如果它有问题,那么很高兴知道;正如在 cmets 中张贴的外部霓虹灯。
  • 我并没有说 user802421 的回答是错误的,事实上我在发表这篇文章之前就对其进行了投票。我要说的是,如果您有一个冻结的 GUI,那么您就会遇到线程问题,并且需要使用后台线程。编辑:也正如 Paul Tomblin 指出的那样——他的答案是 1+。 :)
  • Jeanette (kleopatra),一位 Swing 大师说您应该始终使用 Actions 而不是 ActionListeners,但我不记得她的理由。我使用 Actions 的动机是您正在更改按钮的标题,而 AbstractAction 最初会为您执行此操作(在我的 Convert 构造函数中的 super 调用中)。它还允许您在一行代码中创建具有其行为和标题的 JButton:add(new JButton(new Convert("GO", "Working...")));
【解决方案2】:

在事件线程中发生“ActionPerformed”回调,通常在它返回之前,GUI 上的任何内容都不会更新。如果你想更新 gui,做一些事情,然后再次更新,你将需要更新 gui 并产生一个线程并返回。然后线程必须执行它的操作,然后执行 SwingUtilities.invokeLater 来更新 gui。

【讨论】:

  • 感谢您的解释。这是上面建议的代码的一个很好的附加组件,并解释了为什么需要它。 :)
【解决方案3】:

试试SwingUtilities.invokeLater()

private class convert implements ActionListener {
public void actionPerformed(ActionEvent e) {
    final JButton button = (JButton)e.getSource();

    button.setText("Working...");
    button.setEnabled(false);

    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            anObject name = new AnObject();
            boolean result = name.methodName(chooser.getSelectedFile(),encoding);

            // A bunch of stuff was here but irrelevant to the question,
            // so it was removed to save room.

            button.setEnabled(true);
            button.setText("Convert");
        }
    });
}

【讨论】:

  • 请注意,这在 EDT 中运行 - 如果 methodName 需要很长时间,您的应用程序将冻结。如果您有一个长时间运行的作业,请使用 SwingWorker,并在 done() 方法中更新标题栏。
  • 解决了,谢谢!对 Java 相当陌生,所以我仍在学习一些诸如此类的怪癖。
  • @extraneon - 也谢谢。鉴于操作的长度取决于文件 methodName 正在处理的长度,我可能需要实施您的建议。我想知道“长期工作”需要多长时间...... ;)
  • -1,这是错误的。在此示例中,当您使用 SwingUtilities 时,您已经在 EDT 中。
  • @Moonbeam:如果从事件分派线程调用​​ invokeLater ——例如,从 JButton 的 ActionListener —— doRun.run() 仍将被推迟,直到所有未决事件都已处理完毕。请注意,如果 doRun.run() 抛出未捕获的异常,则事件调度线程将展开(而不是当前线程)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
  • 1970-01-01
  • 2020-03-05
  • 2014-02-07
  • 1970-01-01
相关资源
最近更新 更多