【问题标题】:Swing Worker not refreshing JFrame properlySwing Worker 未正确刷新 JFrame
【发布时间】:2013-11-05 17:10:05
【问题描述】:

我最初尝试在 Java Action Listener 中多次更新 JFrame 和 JPanel,但只有在 Action Listener 完成所有任务时才会更新。这是我原来的问题(Refreshing a JFrame while in an Action Listener)的链接。

在对该问题的反馈中,有人告诉我 Swing Worker 应该解决我的问题。然而,当我实现 Swing Worker(如下所示)时,没有任何改变。只有当 Action Listener 完成所有任务时,JFrame 和 JPanel 才会更新。我的问题是,我是否遗漏了以下内容?如果没有,我如何在 Action Listener 中实现这一点,以便及时正确更新 Frame 和 Panel?

@Override
protected Integer doInBackground() throws Exception{
    //Downloads and unzips the first video.  
    if(cameraBoolean==true)
        panel.add(this.downloadRecording(camera, recording));
    else
        panel.add(new JLabel("Could not contact camera "+camera.getName()));

    panel.repaint();
    jframe.repaint();
    return 1;
}

private JLabel downloadRecording(Camera camera, Recording recording){
    //does a bunch of calculations and returns a jLabel, and works correctly
}

protected void done(){
    try{
        Date currentTime = new Timestamp(Calendar.getInstance().getTime().getTime());
        JOptionPane.showMessageDialog(jframe, "Camera "+camera.getName()+" finished downloading at "+currentTime.getTime());
    }catch (Exception e){
    e.printStackTrace();
    }
}

【问题讨论】:

  • 你不应该在任何swing对象上调用任何方法,也不应该从doInBackground方法内部构造任何swing对象,也不应该直接调用任何这样做的方法。您对.add.repaintnew JLabeldoInBackground 呼叫均违反此规则。只能从 SwingWorker 的 processdone 中修改 gui。

标签: java swing jframe swingworker


【解决方案1】:

您对SwingWorker 的工作方式存在误解。此类旨在提供一种在执行繁重任务时更新 GUI 的方法。所有这一切都是因为 Swing 组件更新发生在 Event Dispatch Thread(又名 EDT),这是一个特定的线程。

例如,如果您单击一个按钮并在 EDT 中执行一项耗时的任务,则该线程将阻塞,直到该任务完成。因此,您会看到您的 GUI 被冻结。

记住这一点,doInBackground() 方法在另一个不是 EDT 的不同线程中运行,这是可以的。所以不要在那里调用任何Swing 方法:

protected Integer doInBackground() throws Exception{
    //Downloads and unzips the first video.  
    if(cameraBoolean==true) // just use if(cameraBoolean), since this is a boolean
        panel.add(this.downloadRecording(camera, recording)); // NO!
    else
        panel.add(new JLabel("Could not contact camera "+camera.getName())); //NO!

    panel.repaint(); //NO, never!
    jframe.repaint();//NO, never!
    return 1;
}

JLabel 添加到此panel 之前执行您的SwingWorker 并使用publish()process() 方法更新其文本:

JPanel panel = new JPanel();
final JLabel progressLabel = new JLabel("Some text before executing SwingWorker");
panel.add(progressLabel);

SwingWorker<Integer, String> worker = new SwingWorker<Integer, String>() {    
    @Override
    protected Integer doInBackground() throws Exception {
        if(cameraBoolean){
            pubish("Starting long process...");
            //Some processing here
            publish("Intermediate result to be published #1");
            //Some other processing stuff
            publish("Intermediate result to be published #2");
            //And so on...
            return 0;
        } else {
            publish("Could not contact camera "+camera.getName());
            return -1;
        }
    }

    @Override
    protected void process(List<String> chunks) {
        for(String string : chunks){
            progressLabel.setText(string);
        }
    }

    @Override
    protected void done() {
        progressLabel.setText("Finished!!!");
    }
};

worker.execute();

process()done() 方法都发生在 EDT 中,因此在那里进行 GUI 更新是安全的。看看这个优秀的例子:Swing Worker Example 了解更多详情。

【讨论】:

  • 这是有道理的。我在想发布在某种程度上是关键,我也想知道在 SwingWorker 中使用 Swing 组件。不过,我还有一个问题是,我需要在 .doInBackground() 中调用 this.proccess() 吗?
  • 您好!不,实际上您从不明确调用 process() 方法。每次调用publish() 方法都会将一个值(块)放入队列中,SwingWorker 本身将通过process() 方法异步处理该值。这样SwingWorker 确保process() 方法在EDT 中运行。所以你只需要定义process() 方法必须做什么,但你永远不会明确地调用它。 @austinthemassive
  • 好的,有道理。既然您说 process() 在 EDT 中,我可以将标签添加到那里的面板吗?
  • 我做到了,现在可以了!感谢您的跟进。当我有一些额外的时间时,我打算更新这个线程。简而言之,我对 Swing Worker 的理解已经结束,但现在我可以开始了。
  • 这个答案让我成为世界上最幸福的人,gratz。
【解决方案2】:

也许是因为您在同步调用 this.downloadRecording(camera, recording) 完成时重新绘制了面板/框架?

尝试只将此调用放入 doInBackground() 方法中,因为(所以我猜)这是需要很长时间的调用,并且在所有这些时间里 JFrame 都不会刷新。

【讨论】:

    【解决方案3】:

    下一个方式不能更新 UI:

     panel.repaint();
     jframe.repaint();
    

    在您的doInBackground 方法中,您必须调用publish(V... chunks) 方法,即Sends data chunks to the process(java.util.List&lt;V&gt;) method.(根据文档),而不是在方法process(List&lt;V&gt; chunks) 中,您可以更新您的UI(根据文档处理方法-Receives data chunks from the publish method asynchronously on the Event Dispatch Thread.)。 SwingWorker docs.

    因此,重写process 方法进行更新,并调用publish 方法。

    您还可以将 Executors 用于后台进程。在这种情况下,您的 UI 将在 EDT 中运行,而您的后台进程将在另一个线程中运行。示例:

    Executors.newSingleThreadExecutor().execute(new Runnable() {
    
            @Override
            public void run() {
                // run background process
    
            }
        });
    

    编辑:good example of SwingWorker

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-12
      • 2019-10-16
      • 2013-10-22
      • 1970-01-01
      • 1970-01-01
      • 2017-11-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多