4

我有一个包含 JLabel 的模式对话框。此对话框调用另一个类中的方法,该方法执行长时间运行的查找活动。我希望这个查找类更新从模态对话框传递的 JLabel。

我尝试从查找过程中的代码循环和 Timer 中更新 JLabel。(我发现当模式对话框运行时,Swing 计时器的 actionPerformed 方法永远不会被调用。)自从实现了 Timer,我将查找过程移到了一个单独的线程中。

我的查找有效,但直到过程结束,JLabel 仍然没有重新绘制。我将不胜感激任何帮助。

这是相关的方法。DictionaryTuple 是一个 POJO,包含两个字段:一个 List 和一个 String。只是 getter 和 setter,但 dictionaryThread 确实可以访问 tupleList;请注意,在 dictionaryThread 完成之前,我们不会在这里触摸它。

public List<DictionaryTuple> findTuples() {
    tupleList = new ArrayList<DictionaryTuple>();
    Thread dictionaryThread = new Thread(this);  // This is the long-running lookup thread
    dictionaryThread.start();

    progressTimer = new Timer();
    progressTimer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
                       //  progressCaption is updated in dictionaryThread
            synchronized (progressCaption) {
                lblProgress.setText(progressCaption);
            }
            lblProgress.repaint();
            Thread.yield();
        }
    }, 250, 250);

     try {
        dictionaryThread.join();
    } catch (InterruptedException e) {
    } finally {
      progressTimer.cancel();
      lblProgress.setText("");
      progressCaption = "";
    }
    return tupleList;
}

我也尝试过这种变化;在这种情况下,内部的 run() 调用将一直保持到处理完成:

    progressTimer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            synchronized (progressCaption) {
            System.out.println("Fire!");
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Update");
                    lblProgress.setText(progressCaption);
                    lblProgress.repaint();
                }});
            }
        }
    }, 250, 250);
4

3 回答 3

2

这个演示应该会有所帮助。如果我通过调用 run 与在单独的线程中运行它来运行长作业,请查看不同之处


    public static void modalDialogTest() {
        JDialog dialog = new JDialog((JFrame)null);
        final JLabel jl = new JLabel("Need some space");
        JButton jb = new JButton ("Click me");
        Timer t = new Timer(1000, new AbstractAction() {
            int it = 0;
            @Override
            public void actionPerformed(ActionEvent arg0) {
                // TODO Auto-generated method stub
                jl.setText("" + it);
                jl.repaint();
                it++;
            }

        });
        t.start();

        AbstractAction wait = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Runnable r = new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                };
                //r.run();
                new Thread(r).start();
            }
        };

        jb.addActionListener(wait);
        dialog.getContentPane().add(jl);
        dialog.getContentPane().add(jb);
        dialog.getContentPane().setLayout(new FlowLayout());
        dialog.pack();
        dialog.setVisible(true);
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
    }
于 2012-07-03T20:25:42.660 回答
2

您需要准确地向我们展示将对话框设置为可见的位置——是在调用方法之前还是之后findTuples()?这很关键。

建议:

  • 始终在 Swing 事件线程或 EDT(事件调度线程)上更新 JLabel。
  • 不要使用 java.util.Timer 来更新 Swing 应用程序的组件。
  • 您可以使用 javax.swing.Timer 来更新 Swing 组件,但这在这里不起作用,除非您使用它来轮询后台进程。
  • 考虑将 SwingWorker 用于后台线程,然后通过 worker 的发布/处理方法或通过添加到 SwingWorker 的 PropertyChangeListener 更新 JLabel。
  • 在将对话框设置为可见之前,在对话框运行时调用您想要运行的所有方法。
于 2012-07-03T20:14:25.183 回答
1

谢谢大家的建议。我实施的是 Hovercraft 的,使用 SwingWorker。

我的方法所在的 Dictionary 类现在扩展了 SwingWorker。findTuples() 方法变为 doInBackground()。不再需要计时器和我自己的线程代码。

doInBackground() 使用带有属性名称“progress”的 publish() 来提供要出现在更新的 JLabel 中的文本。

回到对话代码中,调用涉及创建 Dictionary 实例,并使用 PropertyChangeListener 处理 JLabel(名为 lblProgress)中的更改并检测处理何时完成:

private void myCallingMethod() {
    final Dictionary dictionary = new Dictionary();

    dictionary.addPropertyChangeListener(new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent event) {
            List<DictionaryTuple> tupleList;

            // Is processing complete?
            if (event.getNewValue() instanceof StateValue
                    && event.getNewValue() == StateValue.DONE) {
                try {
                    tupleList = dictionary.get();
                    lblProgress.setText(tupleList.size() + " Tuples Found");
                } catch (Exception e) {
                    JOptionPane.showMessageDialog(
                            null,
                            "Error in processing dictionary: "
                                    + e.getMessage());
                    tupleList = new ArrayList<DictionaryTuple>();
                    lblProgress.setText("");
                }

                // Process the results.
                processTuples(tupleList);

            } else {
                // The process isn't done yet.
                // Is this a request to change lblProgress?
                if (event.getPropertyName().equals("progress")) {  
                    lblProgress.setText((String) event.getNewValue());
                }
            }
        }
    });

    // Start the dictionary lookup to run asynchronously.
    dictionary.execute();
}

我学会了不要依赖 SwingWorker.isDone() 来判断 SwingWorker 的处理是否完成。就我而言,PropertyChangeListener 在该状态下被调用了两次:一次是最后一次 publish() 调用的结果,一次是任务实际完成时。

于 2012-07-06T12:50:31.237 回答