Rodrigo Oliveira
Rodrigo Oliveira

Reputation: 33

Update progress bar with data comming from an inner class in Java

I have a primary class, with the swing components, that calls a secondary class that makes lots of stuff that I want to show a progress bar for. How can I do that without having to bring the whole code from the secondary to the primary class?

Here is the simplified code:

public class ProgressBar {

    public static void main(String[] args) {
        GUI gui = new GUI();
        gui.showGUI();
    }
}
import javax.swing.*;

public class GUI {
    private JFrame mainFrame;
    
    public void showGUI() {
        mainFrame = new JFrame();
        mainFrame.setSize(222, 222);
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        
        Primary primary = new Primary();
        mainFrame.add(primary.getPanel());
        mainFrame.setVisible(true);
    }
}
import javax.swing.*;

public final class Primary {
    private JPanel primaryPanel;
    
    public Primary(){
        createPanel();
    }
    
    public void createPanel() {
         primaryPanel = new JPanel();
         primaryPanel.setSize(333, 333);
         
         JTextArea textArea = new JTextArea();
         Secondary secondary = new Secondary();
         textArea.setText(secondary.writeInfo(11));
         primaryPanel.add(textArea);
         
         JProgressBar progressBar = new JProgressBar();
         primaryPanel.add(progressBar);
         
         primaryPanel.setVisible(true);
    }
    
    public JPanel getPanel(){
        return primaryPanel;
    }
}
public class Secondary {
    private String info;
    public int progress;
    
    public Secondary(){
        info = "";
    }
    
    public String writeInfo(int n){
        for(int i=1; i<=n; i++) {
            info += " bla";
            progress = 100*i/n;
            //System.out.println(progress);
        }
        return info;
    }
}

Upvotes: 1

Views: 59

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347184

So, there are two problems.

First, you need some way for Secondary to notify Primary that the progress state has changed. This can most easily be achieved through the use of an observer pattern (in Swing these are often called "listeners")

Second, Swing is single threaded AND not thread safe.

This means that you shouldn't block the Event Dispatching Thread with long running or blocking operations AND that you shouldn't update the UI or any state the UI relies on from outside the Event Dispatching Thread context.

In this situation, the best solution would be to use a SwingWorker

For example...

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new Primary().getPanel());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public final class Primary {
        private JPanel primaryPanel;

        public Primary() {
            createPanel();
        }

        public void createPanel() {
            primaryPanel = new JPanel(new GridBagLayout());
            primaryPanel.setSize(333, 333);

            JTextArea textArea = new JTextArea(10, 20);
            JProgressBar progressBar = new JProgressBar();

            Secondary secondary = new Secondary(new Secondary.LoadObserver() {
                @Override
                public void progressDidChange(Secondary source, int progress) {
                    progressBar.setValue(progress);
                }

                @Override
                public void informationDidUpdate(Secondary source, String info) {
                    textArea.append(" ");
                    textArea.append(info);
                }
            });

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = gbc.REMAINDER;

            primaryPanel.add(new JScrollPane(textArea), gbc);
            primaryPanel.add(progressBar, gbc);

            primaryPanel.setVisible(true);

            secondary.writeInfo(11);
        }

        public JPanel getPanel() {
            return primaryPanel;
        }
    }

    public class Secondary {
        public interface LoadObserver {
            public void progressDidChange(Secondary source, int progress);

            public void informationDidUpdate(Secondary source, String info);
        }

        private LoadObserver observer;

        public Secondary(LoadObserver observer) {
            this.observer = observer;
        }

        public void writeInfo(int n) {
            SwingWorker<String, String> worker = new SwingWorker<>() {
                @Override
                protected String doInBackground() throws Exception {
                    // Inject a small delay to allow the UI time to load
                    // Demonstration purposes only
                    Thread.sleep(1000);
                    String info = "";
                    for (int i = 1; i <= n; i++) {
                        String data = "bla";
                        publish(data);
                        info += " " + data;
                        setProgress(100 * i / n);
                        // Inject a small delay to allow the UI time for the UI 
                        // to update
                    // Demonstration purposes only
                        Thread.sleep(100);
                    }
                    return info;
                }

                @Override
                protected void process(List<String> chunks) {
                    if (observer == null) {
                        return;
                    }
                    for (String value : chunks) {
                        observer.informationDidUpdate(Secondary.this, value);
                    }
                }
            };
            worker.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
                        if (observer == null) {
                            return;
                        }
                        observer.progressDidChange(Secondary.this, worker.getProgress());
                    } else if ("state".equalsIgnoreCase(evt.getPropertyName())) {
                        switch (worker.getState()) {
                            case DONE:
                                // Oppurtunity to notify oberser that work has
                                // been completed
                                break;
                        }
                    }
                }
            });
            worker.execute();
        }
    }
}

Upvotes: 1

levangode
levangode

Reputation: 426

You don't need to bring the whole code from Primary to Secondary class. You can just pass in the ProgressBar to the Secondary class in Constructor and update it from there according to your logic.

Secondary secondary = new Secondary(progressBar);

However, depending on the logic you are implementing in Secondary Class, this may be against Single Responsibility Principle. So instead, what you can also do is to implement the Observer Pattern and notify the Primary Class from your Secondary class every time the progress needs to be updated.

Upvotes: 0

Related Questions