SwingWorker does not update JProgressBar without Thread.sleep() in custom dialog panel
Asked Answered
S

2

4

I have a SwingWorker class which loads a text file and slices it to chunks for further processing.

This is the SwingWorker class:

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

This works fine when the Thread.sleep(1); is uncommented. However, when I comment the Thread.sleep(1); the class does not update the progressbar.

I call my class thorugh a button, here is the 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();

            }
        }
    });

This should work fine without the sleep, so what is that I am doing wrong here?

UPDATE:

It turned out that my DialogPanel is not the best, and cause this behaviour.

Here is the DialogPanel class:

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

It is probably a mess for professional eyes. How can I show the progressbar in a dialog which will have a confirm button to dispose the dialog when the process is done?

Solution:

I have changed from my DialogPanel class to ProgressMonitor and now everything is fine. Thank you for your time and advices.

Seminole answered 10/11, 2014 at 13:14 Comment(2)
You have shown a method completely outside of the context of its place in the SwingWorker mechanism. You can't be helped without further information.Waltman
Thw whole SwingWorker class is available now.Seminole
S
3

I wanted to track my SwingWorker's progress with a JProgressBar within a JDialog. However my SwingWorker class could not handle my custom DialogPanel class. To achieve the same result, using the default ProgressMonitor class was the best option.

I have passed the ProgressMonitor to the SwingWorker through its constructor:

private final File f;
private final ProgressMonitor pm

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

and changed the following methods like this:

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

The task's propertyChangeListener changed like this:

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

Thanks to trashgod for the answer and comments about the state property.

Seminole answered 12/11, 2014 at 9:51 Comment(2)
+1 Excellent choice for skipping a short delay; more here.Pickerelweed
In additon, if you want to costumize your ProgressMonitor the following question is helpful. It helped me change the Cancel button, the title, and the icon of the dialog.Seminole
P
6

The setProgress() API notes: "For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only." Adding Thread.sleep(1) simply defers the coalescence; invoking println() introduces a comparable delay. Take heart that your file system is so fast; I would be reluctant to introduce an artificial delay. As a concrete example that illustrates the effect, I added intermediate reporting to this complete example, as shown below.

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);
        }
    }
});
Pickerelweed answered 10/11, 2014 at 22:37 Comment(5)
SwingWorker.StateValue s where do you use this variable? I see in the other example that you linked, that this is used when we set the pb indeterminate. But what is it supposed to do here?Seminole
SwingWorker.StateValue is convenient for managing indeterminate progress before and after intermediate progress; here it should be tested for completion.Pickerelweed
I tried your suggestion, my progressbar still freeze sometimes. However, it turned out it only occurs when I load textfiles with few lines (500 - 1000 line). The progress bar starts loading and stops at a random value (like 64% or 9%). Depending on this with small files the progressbar should jump to the final value at once instead of freezing at random values, should not it?Seminole
I see the same, stochastic effect; because the final progress is not guaranteed, use the "state" property.Pickerelweed
I tried that as well, it did not help. My crapy dialogPanel is the problem, but I will use the state property from now. Thank you for your help, I appreciate it ^^.Seminole
S
3

I wanted to track my SwingWorker's progress with a JProgressBar within a JDialog. However my SwingWorker class could not handle my custom DialogPanel class. To achieve the same result, using the default ProgressMonitor class was the best option.

I have passed the ProgressMonitor to the SwingWorker through its constructor:

private final File f;
private final ProgressMonitor pm

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

and changed the following methods like this:

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

The task's propertyChangeListener changed like this:

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

Thanks to trashgod for the answer and comments about the state property.

Seminole answered 12/11, 2014 at 9:51 Comment(2)
+1 Excellent choice for skipping a short delay; more here.Pickerelweed
In additon, if you want to costumize your ProgressMonitor the following question is helpful. It helped me change the Cancel button, the title, and the icon of the dialog.Seminole

© 2022 - 2024 — McMap. All rights reserved.