尝试在模态对话框中更新JLabel

时间:2012-07-03 20:06:15

标签: java swing concurrency jlabel event-dispatch-thread

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

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

我的查找工作正常,但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);

3 个答案:

答案 0 :(得分:2)

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

<强>建议:

  • 始终在Swing事件线程或EDT(事件派发线程)上更新JLabel。
  • 不要使用java.util.Timer来更新Swing应用程序的组件。
  • 您可以使用javax.swing.Timer来更新Swing组件,但这不会在这里工作,除非您使用它来轮询后台进程。
  • 考虑使用SwingWorker进行背景线程处理,然后通过工作者的发布/处理方法或通过添加到SwingWorker的PropertyChangeListener来更新JLabel。
  • 在对话框运行 之前调用您想要运行的所有方法 将对话框设置为可见。

答案 1 :(得分:2)

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


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

答案 2 :(得分:1)

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

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

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在该状态下被调用两次:一次是使用上一次发布()调用的结果,一次是在任务实际完成时。