1

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;
    }
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • 2
    Oracle has a helpful tutorial, [Creating a GUI With Swing](https://docs.oracle.com/javase/tutorial/uiswing/index.html). Skip the Learning Swing with the NetBeans IDE section. Pay particular attention to the [How to Use Progress Bars](https://docs.oracle.com/javase/tutorial/uiswing/components/progress.html) section. – Gilbert Le Blanc May 01 '23 at 21:00
  • Thank you, Gilbert! I will read that tutorial. – Rodrigo Oliveira May 02 '23 at 12:29
  • My question has a negative feedback (one, so far). I wonder why... – Rodrigo Oliveira May 02 '23 at 12:31

2 Answers2

1

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();
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • You're right! And it works. I only marked Levan Goderdzishvili's answer beacuse it helped me in a straightforward way. Later I will have to deal with the limitations of swing, beacuse my real project is a lot more complex than showing "bla" on the screen. Thank you! I'm sorry I couldn't leave a positive feedback because I don't have the minimum reputation yet. – Rodrigo Oliveira May 02 '23 at 12:26
0

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.

levangode
  • 406
  • 2
  • 12