【问题标题】:How to update/paint JProgressBar while Swing is loaded building the GUI如何在 Swing 加载构建 GUI 时更新/绘制 JProgressBar
【发布时间】:2011-10-28 01:41:44
【问题描述】:

我有一个 GUI,它在运行它的平台上构建/初始化非常繁重。因此我想在它初始化时更新进度..

我有一个未装饰的小型 JDialog,其中包含一个 JLabel 和一个 JProgressBar,我想在初始化期间在特定位置更新它们,但是,因为事件调度 thead(根据 Swing 规则)用于构建/初始化 GUI,进度当然在 EDT 再次空闲(即初始化完成)之前不会更新..

我已经使用“paintImmediately”重绘了 JProgressBar,但我似乎无法让它对 JLabel 和对话框本身正常工作。有没有什么简单的推荐/经过验证的方法来完成这个?

干杯...

编辑:添加一个我正在尝试做的示例;当然,大大简化了。

private JLabel progressLabel;
private JProgressBar progressBar;

public static int main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showProgressDialog();
            progressLabel.setText("construct 1");

            constructSomeHeavyGUI();

            progressLabel.setText("construct 2");
            progressBar.setValue(33);

            constructSomeMoreHeavyGUI();

            progressLabel.setText("construct 3");
            progressBar.setValue(67);

            constructEvenMoreHeavyGUI();

            progressLabel.setText("done");
            progressBar.setValue(100);

            hideProgressDialog();

            showHeavyGUI();

        }
    });
}

只要 EDT 忙,上面调用 progressBar.setValue()/progressLabel.setText() 引起的重绘当然会排队,并在我们全部完成后导致重绘,而不是一路更新..

【问题讨论】:

  • “大大简化了,当然” 要发布为回答者大大简化的代码,请发布SSCCE(当然)。

标签: java swing event-dispatch-thread background-thread


【解决方案1】:

我建议通过使用 SwingWorker ,然后您可以在 EDT 上正确更新 JProgressBar 并且没有任何冻结或使用 Concurency in Swing 的问题,

还有另一个选择是使用Runnable#thread,但是你必须将所有输出到GUI 包装到invokeLater();

例如:

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;

public class TestProgressBar {

    private static void createAndShowUI() {
        JFrame frame = new JFrame("TestProgressBar");
        frame.getContentPane().add(new TestPBGui().getMainPanel());
        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() {

            @Override
            public void run() {
                createAndShowUI();
            }
        });
    }

    private TestProgressBar() {
    }
}

class TestPBGui {

    private JPanel mainPanel = new JPanel();

    public TestPBGui() {
        JButton yourAttempt = new JButton("WRONG attempt to show Progress Bar");
        JButton myAttempt = new JButton("BETTER attempt to show Progress Bar");
        yourAttempt.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                yourAttemptActionPerformed();
            }
        });
        myAttempt.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                myAttemptActionPerformed();
            }
        });
        mainPanel.add(yourAttempt);
        mainPanel.add(myAttempt);
    }

    private void yourAttemptActionPerformed() {
        Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
        JDialog progressDialog = new JDialog(thisWin, "Uploading...");
        JPanel contentPane = new JPanel();
        contentPane.setPreferredSize(new Dimension(300, 100));
        JProgressBar bar = new JProgressBar(0, 100);
        bar.setIndeterminate(true);
        contentPane.add(bar);
        progressDialog.setContentPane(contentPane);
        progressDialog.pack();
        progressDialog.setLocationRelativeTo(null);
        Task task = new Task("Your attempt");
        task.execute();
        progressDialog.setVisible(true);
        while (!task.isDone()) {
        }
        progressDialog.dispose();
    }

    private void myAttemptActionPerformed() {
        Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
        final JDialog progressDialog = new JDialog(thisWin, "Uploading...");
        JPanel contentPane = new JPanel();
        contentPane.setPreferredSize(new Dimension(300, 100));
        final JProgressBar bar = new JProgressBar(0, 100);
        bar.setIndeterminate(true);
        contentPane.add(bar);
        progressDialog.setContentPane(contentPane);
        progressDialog.pack();
        progressDialog.setLocationRelativeTo(null);
        final Task task = new Task("My attempt");
        task.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equalsIgnoreCase("progress")) {
                    int progress = task.getProgress();
                    if (progress == 0) {
                        bar.setIndeterminate(true);
                    } else {
                        bar.setIndeterminate(false);
                        bar.setValue(progress);
                        progressDialog.dispose();
                    }
                }
            }
        });
        task.execute();
        progressDialog.setVisible(true);
    }

    public JPanel getMainPanel() {
        return mainPanel;
    }
}

class Task extends SwingWorker<Void, Void> {

    private static final long SLEEP_TIME = 4000;
    private String text;

    public Task(String text) {
        this.text = text;
    }

    @Override
    public Void doInBackground() {
        setProgress(0);
        try {
            Thread.sleep(SLEEP_TIME);// imitate a long-running task
        } catch (InterruptedException e) {
        }
        setProgress(100);
        return null;
    }

    @Override
    public void done() {
        System.out.println(text + " is done");
        Toolkit.getDefaultToolkit().beep();
    }
}

编辑:

1) 你展示了另一个问题,你为什么要在运行/运行时创建大量顶级容器,只创建所需数量的容器并由 removeAll() 重新使用它

2) here 可能是您需要的,JTable 中的所有 JProgressBars 都非常易于访问和配置

3) 这是你的paintImmediately(),这就是为什么不将任何进度绘制到JLabel 而是使用JProgressBar#setValue(int); 的真正原因,

【讨论】:

  • 那行不通。如果你仔细阅读我的帖子,你会注意到我知道所有的 Swing/EDT 线程问题。我不能把长时间运行的代码放在上面一个不同的线程,因为它是 GUI 构造代码,它必须根据 Swing 规则在 EDT 上运行。我所追求的实际上是在我已经在 EDT 上运行时(重新)绘制我的进度对话框的直接方法..
  • 1) 请不要将paintImmediately() 用于Conpound JComponet,只有当您能够为某些JComponents 创建自己的UI 时,2) 您能否展示(我的好奇心)您如何实现paintImmediately() , 因为这只能在 EDT 存在的情况下使用并且你总是需要测试 if(SwingUtilities.isEventDispatchThread()) 3) 不要重新发明轮子,JProgressbar 可以通过实现的方法轻松做到这一点
  • 1) 好的,但是我应该使用什么? 2)/3) 请参阅我原帖中的编辑示例。
【解决方案2】:

constructSome*HeavyGUI() 可能确实需要足够长的时间才有意义,但更多可能是填充数据模型是问题所在。相反,构建并显示空的 GUI 元素并启动一个或多个 SwingWorker 实例来编组每个元素的数据。有相关的例子herehere

附录:如果问题在于实例化组件,而不是加载数据模型,您可以将调用链接到invokeLater(),如下面的评论中建议的那样。如果您要实例化那么多 组件,请考虑flyweight patternJTable 是一个熟悉的例子。

【讨论】:

  • 它没有用导致问题的数据填充它,因为在构造过程中没有获取/填充数据。
  • 我没见过。如果您无法卸载任何内容,则必须将调用链接到invokeLater()。例如,让constructHeavy1()invokeLater()constructHeavy2() 结尾,等等。
  • 由于没有更好的选择,我最终选择了“paintImmediately”路线。
【解决方案3】:

将长时间运行的代码移到单独的线程中,并使用 SwingUtilities.invokeAndWait 或 invokeLater 来更新 GUI。

【讨论】:

    【解决方案4】:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-18
      • 1970-01-01
      • 1970-01-01
      • 2012-04-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多