JProgressBar not working properly

2019-03-05 16:18发布

问题:

So my JProgressBar I have set up doesn't work the way I want it. So whenever I run the program it just goes from 0 to 100 instantly. I tried using a ProgressMonitor, a Task, and tried a SwingWorker but nothing I tried works.

Here is my program:

int max = 10;
for (int i = 0; i <= max; i++) {
        final int progress = (int)Math.round(
            100.0 * ((double)i / (double)max)
        );

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
                }
                jProgressBar2.setValue(progress);
            }
        });
    }

@MadProgrammer Here is my attempt at making a swing worker and writing each name to the document and updating the progress bar. The program gets to around 86 percent and stops, never creating the finished document. The program creates a blank document. Here are the two methods first is the SwingWorker object I made:

public class GreenWorker extends SwingWorker<Object, Object> {
    @Override
    protected Object doInBackground() throws Exception {
        int max = greenList.size();
        XWPFParagraph tmpParagraph;
        XWPFRun tmpRun;
        FileInputStream file = 
                new FileInputStream(location + "GreenBandList.docx");
        gDocument = new XWPFDocument(OPCPackage.open(file));
        for (int i = 0; i < max; i++) {
            tmpParagraph = gDocument.getParagraphs().get(0);
            tmpRun = tmpParagraph.createRun();
            if (greenList.get(i).length() == 1) {
                    tmpRun.setBold(true);
                    tmpRun.setText(greenList.get(i));
                    tmpRun.setBold(false);
            } else {
                    tmpRun.setText(greenList.get(i));//Write the names to the Word Doc
            }
            int progress = Math.round(((float) i / max) * 100f);
            setProgress(progress);
        }
        return null;
  }        
}

And here is the code for the button that starts it and has my property change event.

private void GenerateGreenList() throws IOException, InterruptedException {
    //Need to fix the bug that removes the Letter Header in Yellow Band list
    //********************************************************************\\

    //Delete the old list and make a new one
    File templateFile = new File(location + "\\backup\\GreenTemplate.docx");
    FileUtils.deleteQuietly(new File(location + "GreenBandList.docx"));
    FileUtils.copyFile(templateFile, new File(location + 
            "GreenBandList.docx"));
    //Get the New Entries
    String[] entries = jTextPane3.getText().split("\n");
    for (String s : entries) {
        if (s != null) {
            greenList.add(s);
        }
    }
    //Resort the list
    Collections.sort(greenList);
    //Write the names to the document
    GreenWorker worker = new GreenWorker();
    worker.addPropertyChangeListener(new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("progress".equals(evt.getPropertyName())) {
                jProgressBar2.setValue((Integer) evt.getNewValue());
            }
        }
    });
    worker.execute();
    if (worker.isDone()) {
        try {
            gDocument.write(new FileOutputStream(new File(location + "GreenBandList.docx")));
            ////////////////////////////////////////////////////////////
        } catch (IOException ex) {
            Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
        }
        JOptionPane.showMessageDialog(null, "Green Band List Created!");
        jProgressBar2.setValue(0);
    }
}

I used the property change listener from one of your other posts but I don't really understand what the one you wrote does or what it does in general?

回答1:

Swing is a single threaded environment, that is, there is a single thread which is responsible for processing all the events that occur within the system, including repaint events. Should anything block this thread for any reason, it will prevent Swing from processing any new events, including, repaint events...

So all this ...

EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);        }
        jProgressBar2.setValue(progress);
    }
});

Is constantly pausing the Event Dispatching Thread, preventing it from actually doing any updates (or at least spacing them randomly)...

It's also likely that your outer loop is been run from within the context of the EDT, meaning that until it exists, nothing in the Event Queue will be processed. All your repaint requests will be consolidated down to a single paint request and voila, instant filled progress bar...

You really should use a SwingWorker - I know you said you tried one, but you've not shown any code as to your attempt in this regards, so it's difficult to know why it didn't work, however...

  • SwingWorker and JProgressBar example
  • SwingWorker and JProgressBar example
  • SwingWorker and JProgressBar example
  • SwingWorker and JProgressBar example
  • SwingWorker and dual welding JProgressBar example
  • SwingWorker and JProgressBar example

And forgive me if we haven't said this a few times before :P



回答2:

You are evoking Thread.sleep inside the EvokeLater which means that it is running on another thread than your for loop. i.e., your for loop is completing instantaneously (well, however long it takes to loop from 1 to 100, which is almost instantaneously).

Move Thread.sleep outside of EvokeLater and it should work as you intend.

    int max = 10;
    for (int i = 0; i <= max; i++) {
        final int progress = (int)Math.round(
            100.0 * ((double)i / (double)max)
        );

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                jProgressBar2.setValue(progress);
            }
        });
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

Edit: agree with @MadProgrammer. It appears this is just an illustrative question, but you should make sure whatever you're trying to accomplish here you use a SwingWorker for.