在自定义对话框面板中,如果没有Thread.sleep(),SwingWorker无法更新JProgressBar。

4

我有一个SwingWorker类,它加载文本文件并将其切片以进行进一步处理。

以下是SwingWorker类:

public class ConverterWorker extends SwingWorker<String, String>
{
private final File f;
private final JLabel label;

public ConverterWorker(File f, JLabel label)
{
    this.f = f;
    this.label = label;
}

@Override
protected String doInBackground() throws Exception 
{
    NMTMain.convertableData = getDataSets(f);

    if(!NMTMain.convertableData.isEmpty())
    {
        return "Done";
    }
    else
    {
        publish("Failed to load the file!");
        return "Failed";
    }
}

@Override
public void done() 
{
    try 
    {
        label.setText(get());
    } 
    catch (Exception e)
    {
        e.printStackTrace(System.err);
        System.out.println("error");
    }
}

@Override
protected void process(List<String> chunks)
{
    label.setText(chunks.get(chunks.size() - 1));
}

public ArrayList<ArrayList<Convertable>> getDataSets(File f)
{
    ArrayList<ArrayList<Convertable>> dataSets = new ArrayList<ArrayList<Convertable>>();

    publish("Loading file...");
    setProgress(0);

    String[] data = loadFile(f);

    for(int i = 0; i< NMTMain.nodes.size(); i++)
    {
        dataSets.add(splitByNode(data, NMTMain.nodes.get(i).getName()));
    }

    setProgress(100);

    return dataSets;
}

private ArrayList<Convertable> splitByNode(String[] data, String name)
{
    ArrayList<Convertable> temp = new ArrayList<Convertable>();

    for(int i = 0; i < data.length; i++)
    {
        if(data[i].contains(name))
        {
            temp.add(new Convertable(data[i]));
        }
    }

    Collections.sort(temp);

    return temp;
}

private String[] loadFile(File f)
{
    String data = "";
    String[] nodes; 

    long fileLength = f.length();
    int bytesRead = -1;
    int totalBytesRead = 0;

    try 
    {
        if(f.exists()) 
        {
            Scanner scan = new Scanner(f);

            while(scan.hasNextLine()) 
            {
                String line = scan.nextLine();
                data = data + line + "\n";
                bytesRead = line.getBytes().length;
                totalBytesRead += bytesRead;
                int progress = (int) Math.round(((double) totalBytesRead / (double) fileLength) * 100d);

               /* try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }*/

                //publish("loading... " + String.valueOf(progress));
                setProgress(progress);
            }

            scan.close();
        }

    }
    catch (FileNotFoundException e) 
    {
        // TODO Auto-generated catch block
                e.printStackTrace();
    }

    nodes = data.split("\n\"\n");

    return nodes;        
}

当取消注释Thread.sleep(1);时,它可以很好地工作。但是,当我注释掉Thread.sleep(1);时,该类不会更新进度条。

我通过一个按钮调用我的类,这里是ActionListener

loadInput.addActionListener(new ActionListener()
    {
        @Override
        public void actionPerformed(ActionEvent arg0)
        {   
            int returnval=NMTMain.fileChooser.showOpenDialog(NMTMain.MainFrame);

            if(returnval == 0)
            {
                File f=NMTMain.fileChooser.getSelectedFile();

                final ConverterWorker worker = new ConverterWorker(f, dialogPanel.getLabel());

                worker.addPropertyChangeListener(new PropertyChangeListener()
                {
                    @Override
                    public void propertyChange(final PropertyChangeEvent evt)
                    {
                        if("progress".equalsIgnoreCase(evt.getPropertyName())) 
                        {
                            dialogPanel.showProgressDialog("Conversion");
                            dialogPanel.setProgressBarValue((int) evt.getNewValue());
                        }

                        if(worker.isDone())
                        {
                            dialogPanel.showConfirmDialog("Conversion", "OK");
                        }
                    }
                });
                worker.execute();

            }
        }
    });

没有sleep也应该正常工作,那么我在这里做错了什么?

更新:

事实证明我的DialogPanel不是最好的,会导致这种行为。

这是DialogPanel类:

public class DialogPanel extends JDialog
{

private JLabel label;
private JPanel panel;
private JButton button;
private JProgressBar progressBar;

public DialogPanel()
{
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.insets = new Insets(5,10,5,10);

    panel = new JPanel();
    panel.setLayout(new GridBagLayout());

    progressBar = new JProgressBar(0,100);
    progressBar.setVisible(false);
    progressBar.setStringPainted(true);

    setLabel(new JLabel("def text", SwingConstants.CENTER));

    button = new JButton("OK");
    button.setVisible(false);

    button.addActionListener(new ActionListener()
    {
        @Override
        public void actionPerformed(ActionEvent arg0)
        {
            // TODO Auto-generated method stub
            dispose();
        }
    });

    gbc.gridx = 0;
    gbc.gridy = 0;
    panel.add(getLabel(), gbc);
    gbc.gridy = 1;
    panel.add(progressBar, gbc);
    panel.add(button, gbc);

    this.setContentPane(panel);
    this.setLocationRelativeTo(null);
    this.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
    this.setResizable(false);
}

public void setProgressBarValue(int value)
{
    progressBar.setValue(value);
}

public void setProgressBarVisibility(boolean value)
{
    progressBar.setVisible(value);
}

public void setText(String text)
{
    getLabel().setText(text);
}

public void showProgressDialog(String title)
{
    progressBar.setVisible(true);
    button.setVisible(false);
    this.setTitle(title);
    this.pack();

    if(!this.isVisible())
    {
        this.setVisible(true);
    }
}

public void showConfirmDialog(String title, String buttontext)
{
    progressBar.setVisible(false);
    button.setVisible(true);
    this.setTitle(title);
    button.setText(buttontext);
    this.pack();

    if(!this.isVisible())
    {
        this.setVisible(true);
    }       
}

public JLabel getLabel() 
{
    return label;
}

public void setLabel(JLabel label)
{
    this.label = label;
}

public JProgressBar getProgressBar() 
{
    return progressBar;
}

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

对于专业人士来说,这可能是一团糟的。我如何在对话框中显示进度条,并在进程完成时有一个确认按钮以处理对话框?

解决方案:

我已经从我的DialogPanel类改为ProgressMonitor,现在一切都很好。 感谢您的时间和建议。


您展示了一个完全脱离其在“SwingWorker”机制中的位置的方法。如果没有更多信息,我们无法提供帮助。 - Marko Topolnik
现在整个“SwingWorker”类都可用了。 - Bence Kaulics
2个回答

6
setProgress() API指出:"为了提高性能,所有这些调用都会被合并成一个仅带有最后一个调用参数的调用。" 添加Thread.sleep(1)只是推迟了合并; 调用println()引入了可比较的延迟。请放心,您的文件系统非常快; 我不愿意引入人为的延迟。作为说明效果的具体示例,我向此完整的示例添加了中间报告,如下所示。
private static class LogWorker extends SwingWorker<TableModel, String> {
    private long fileLength;
    private long bytesRead;
    ...
    this.fileLength = file.length();
    ...
    while ((s = br.readLine()) != null) {
        publish(s);
        bytesRead += s.length();
        int progress = (int)(100 * bytesRead / fileLength);
        // System.out.println(progress);
        setProgress(progress);
    }
    ...
}

lw.addPropertyChangeListener((PropertyChangeEvent e) -> {
    if ("progress".equals(e.getPropertyName())) {
        jpb.setValue((Integer)e.getNewValue());
    }
    if ("state".equals(e.getPropertyName())) {
        SwingWorker.StateValue s = (SwingWorker.StateValue) e.getNewValue();
        if (s.equals(SwingWorker.StateValue.DONE)) {
            jpb.setValue(100);
        }
    }
});

SwingWorker.StateValue s 这个变量在哪里使用?我在您链接的另一个示例中看到它用于设置pb不确定状态。但是这里它应该做什么? - Bence Kaulics
SwingWorker.StateValue 在处理不确定进度之前和之后的中间进度时非常方便;在此处应该测试其是否完成。 - trashgod
我尝试了你的建议,我的进度条有时仍然会冻结。然而,事实证明,这只发生在加载少量行数(500-1000行)的文本文件时。进度条开始加载并停留在一个随机值上(如64%或9%)。根据这个链接,对于小文件,进度条应该立即跳到最终值,而不是在随机值上冻结,不是吗? - Bence Kaulics
我看到了相同的随机效果;因为最终进展不能保证,所以使用“state”属性。 - trashgod
我也尝试过那个,但没有帮助。我的糟糕的 dialogPanel 是问题所在,但是从现在开始我会使用 state 属性。谢谢你的帮助,非常感激 ^^. - Bence Kaulics

3
我希望能够在JDialog中使用JProgressBar跟踪SwingWorker的进度。然而,我的SwingWorker类无法处理自定义的DialogPanel类。为了实现同样的结果,使用默认的ProgressMonitor类是最好的选择。
我已经通过构造函数将ProgressMonitor传递给SwingWorker
private final File f;
private final ProgressMonitor pm

public FileLoadWorker(File f, ProgressMonitor pm)
{
    this.f = f;
    this.pm = pm;
}

并将以下方法更改为如下方式:
@Override
public void done() 
{
    try 
    {
        pm.setNote(get());
    } 
    catch (Exception e)
    {
        e.printStackTrace(System.err);
        System.out.println("error");
    }
}

@Override
protected void process(List<String> chunks)
{
   pm.setNote(chunks.get(chunks.size() - 1));
}

任务的propertyChangeListener发生了变化,如下所示:
final FileLoadWorker worker = new FileLoadWorker(f, pm);

worker.addPropertyChangeListener(new PropertyChangeListener()
{
    @Override
    public void propertyChange(final PropertyChangeEvent evt)
    {
        if("progress".equalsIgnoreCase(evt.getPropertyName())) 
        {
            pm.setProgress((int) evt.getNewValue());
        }

        if("state".equals(evt.getPropertyName())) 
        {
            SwingWorker.StateValue s = (SwingWorker.StateValue) evt.getNewValue();
            if(s.equals(SwingWorker.StateValue.DONE))
            {
                pm.setProgress(100);
                pm.close();
                Toolkit.getDefaultToolkit().beep();
            }
        }

        if(pm.isCanceled())
        {
            pm.close();
            worker.cancel(true);
        }
    }
});
worker.execute();

感谢trashgod关于state属性的答案和评论。


1
+1 跳过短暂延迟的绝佳选择;更多信息请参见此处:https://docs.oracle.com/javase/tutorial/uiswing/examples/components/ProgressMonitorDemoProject/src/components/ProgressMonitorDemo.java。 - trashgod
1
另外,如果你想要自定义你的 ProgressMonitor,以下这个问题会很有帮助。它帮助我改变了对话框中的 Cancel按钮、标题和图标。 - Bence Kaulics

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接