Tiago
Tiago

Reputation: 733

How can I repaint a label while doing some processing, in Swing?

I'm new to Swing and I was trying to do this:

On pressing a JButton, the program will start iterating over hundreds of items, taking 1 second to process each one, and after finishing each one he should update a label to show the number of items already processed.

The problem is, the label's text is not updated until the cycle finishes iterating over all the items.

I searched online and apparently it's because this is running in the same thread, so I created a new thread to process the data and to update the variable to be used in the label (number of processed files).

But it didn't work. Then I even made another thread, which I start after the previous one, that just repaints the label. Still nothing works.

The code is like this:

btnNewButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            try { SwingUtilities.invokeLater(validateFiles); } 
}); }

Runnable validateFiles = new Runnable() {

    @Override
    public void run() {
                 while(x_is_not_100) {
                      processLoadsOfStuff();
                      label.setText(x); }
            }
};

Can you help me with this?

Upvotes: 3

Views: 4961

Answers (2)

mre
mre

Reputation: 44250

Simple - use a SwingWorker. For more information, read the Tasks that Have Interim Results tutorial.


Here's a pretty generic example that will use a JLabel to display counting from 0 to 30 -

public final class SwingWorkerDemo {
    private static JLabel label = 
        new JLabel(String.valueOf(0), SwingConstants.CENTER);

    public static void main(String[] args){
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                createAndShowGUI();             
            }
        });

        JLabelSwingWorker workerThread = new JLabelSwingWorker();
        workerThread.run();
    }

    private static void createAndShowGUI(){
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.add(label);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static class JLabelSwingWorker extends SwingWorker<Void, Integer>{
        @Override
        protected Void doInBackground() throws Exception {
            for(int i = 1; i < 31; i++){
                Thread.sleep(1000);
                publish(i);
            }
            return null;
        }

        @Override
        protected void process(List<Integer> integers) {
            Integer i = integers.get(integers.size() - 1);
            label.setText(i.toString());
        }
    }
}

Upvotes: 8

JB Nizet
JB Nizet

Reputation: 692231

The background processing must be done in a separate thread. But the label update must be done in the event dispatch thread.

So your code should look like this:

btnNewButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // start a new thread for the background task
        new Thread(validateFiles).start(); 
    }); 
}

Runnable validateFiles = new Runnable() {

    @Override
    public void run() {
             while(x_is_not_100) {
                  processLoadsOfStuff();
                  // use SwingUtilities.invokeLater so that the label update is done in the EDT:
                  SwingUtilities.invokeLater(new Runnable() {
                      @Override
                      public void run() {
                          label.setText(x);
                      }
                  });
             }
    };

But you might want to use the SwingWorker class, which is designed to do that in a simpler way. Its documentation is very well done and contains examples.

Upvotes: 6

Related Questions