Reputation: 33
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
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
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