Is MVC in Swing Thread Safe - java

I'm trying to touch limits of MVC architecture in Swing, but as I tried everything all (from SwingWorker or Runnable#Thread) are done on EDT
my questions:
is there some limits or strictly depends by order of the implementations
(wrapped into SwingWorker or Runnable#Thread) ?
limited is if is JComponent#method Thread Safe or not ?
essential characteristic of an MVC architecture in Swing, ?
inc. Container Re-Layout ?
note: for my SSCCE I take one of great examples by HFOE, and maybe by holding this principes strictly isn't possible to create any EDT lack or GUI freeze
import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.*;
public class MVC_ProgressBarThread {
private MVC_ProgressBarThread() {
MVC_View view = new MVC_View();
MVC_Model model = new MVC_Model();
MVC_Control control = new MVC_Control(view, model);
view.setControl(control);
JFrame frame = new JFrame("MVC_ProgressBarThread");
frame.getContentPane().add(view);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MVC_ProgressBarThread mVC_ProgressBarThread = new MVC_ProgressBarThread();
}
});
}
}
class MVC_View extends JPanel {
private static final long serialVersionUID = 1L;
private MVC_Control control;
private JProgressBar progressBar = new JProgressBar();
private JButton startActionButton = new JButton("Press Me and Run this Madness");
private JLabel myLabel = new JLabel("Nothing Special");
public MVC_View() {
startActionButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buttonActionPerformed();
}
});
JPanel buttonPanel = new JPanel();
startActionButton.setFocusPainted(false);
buttonPanel.add(startActionButton);
setLayout(new BorderLayout(10, 10));
add(buttonPanel, BorderLayout.NORTH);
progressBar.setStringPainted(true);
add(progressBar, BorderLayout.CENTER);
myLabel.setIcon(UIManager.getIcon("OptionPane.questionIcon"));
myLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
add(myLabel, BorderLayout.SOUTH);
}
public void setControl(MVC_Control control) {
this.control = control;
}
private void buttonActionPerformed() {
if (control != null) {
control.doButtonAction();
}
}
public void setProgress(int progress) {
progressBar.setValue(progress);
}
public void setProgressLabel(String label) {
progressBar.setString(label);
}
public void setIconLabel(Icon icon) {
myLabel.setIcon(icon);
}
public void start() {
startActionButton.setEnabled(false);
}
public void done() {
startActionButton.setEnabled(true);
setProgress(100);
setProgressLabel(" Done !!! ");
setIconLabel(null);
}
}
class MVC_Control {
private MVC_View view;
private MVC_Model model;
public MVC_Control(final MVC_View view, final MVC_Model model) {
this.view = view;
this.model = model;
model.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pce) {
if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
view.setProgress((Integer) pce.getNewValue());
}
if (MVC_Model.PROGRESS1.equals(pce.getPropertyName())) {
view.setProgressLabel((String) pce.getNewValue());
}
if (MVC_Model.PROGRESS2.equals(pce.getPropertyName())) {
view.setIconLabel((Icon) pce.getNewValue());
}
}
});
}
public void doButtonAction() {
view.start();
SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
model.reset();
model.startSearch();
return null;
}
#Override
protected void done() {
view.done();
}
};
swingworker.execute();
}
}
class MVC_Model {
public static final String PROGRESS = "progress";
public static final String PROGRESS1 = "progress1";
public static final String PROGRESS2 = "progress2";
private static final int MAX = 11;
private static final long SLEEP_DELAY = 1000;
private int progress = 0;
private String label = "Start";
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private PropertyChangeSupport pcs1 = new PropertyChangeSupport(this);
private PropertyChangeSupport pcs2 = new PropertyChangeSupport(this);
private final String[] petStrings = {"Bird", "Cat", "Dog",
"Rabbit", "Pig", "Fish", "Horse", "Cow", "Bee", "Skunk"};
private int index = 1;
private Queue<Icon> iconQueue = new LinkedList<Icon>();
private Icon icon = (UIManager.getIcon("OptionPane.questionIcon"));
public void setProgress(int progress) {
int oldProgress = this.progress;
this.progress = progress;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS,
oldProgress, progress);
pcs.firePropertyChange(evt);
}
public void setProgressLabel(String label) {
String oldString = this.label;
this.label = label;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS1,
oldString, label);
pcs1.firePropertyChange(evt);
}
public void setIconLabel(Icon icon) {
Icon oldIcon = this.icon;
this.icon = icon;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS2,
oldIcon, icon);
pcs2.firePropertyChange(evt);
}
public void reset() {
setProgress(0);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
pcs1.addPropertyChangeListener(listener);
pcs2.addPropertyChangeListener(listener);
}
public void startSearch() {
iconQueue.add(UIManager.getIcon("OptionPane.errorIcon"));
iconQueue.add(UIManager.getIcon("OptionPane.informationIcon"));
iconQueue.add(UIManager.getIcon("OptionPane.warningIcon"));
iconQueue.add(UIManager.getIcon("OptionPane.questionIcon"));
for (int i = 0; i < MAX; i++) {
int newValue = (100 * i) / MAX;
setProgress(newValue);
setProgressLabel(petStrings[index]);
index = (index + 1) % petStrings.length;
setIconLabel(nextIcon());
try {
Thread.sleep(SLEEP_DELAY);
} catch (InterruptedException e) {
}
}
}
private Icon nextIcon() {
Icon icon1 = iconQueue.peek();
iconQueue.add(iconQueue.remove());
return icon1;
}
}

This is too long for a comment...
First and this is unrelated to the rest of this answer: there are many different MVCs out there and the one you used in that piece of code you posted here is not the same as the one used in the article you linked to: http://www.oracle.com/technetwork/articles/javase/mvc-136693.html
The article correctly points out that it's just "A common MVC implementation" (one where the view registers a listener listening to model changes). Your implementation is a different type of MVC, where the controller registers a listener listening to model changes and then updates the view.
Not that there's anything wrong with that: there are a lot of different types of MVCs out there (*).
(Another little caveat... Your view is aware of your controller in your example, which is a bit weird: there are other ways to do what you're doing without needing to "feed" the controller to the view like you do with your setControl(...) inside your MVCView.)
But anyway... You're basically nearly always modifying the GUI from outside the EDT (which you shouldn't be doing):
public void setIconLabel(final Icon icon) {
myLabel.setIcon(icon);
}
You can check it by adding this:
System.out.println("Are we on the EDT? " + SwingUtilities.isEventDispatchThread());
This is because you're eventually doing these updates from your SwingWorker thread (the SwingWorker thread is run outside the EDT: it's basically the point of a Swing worker).
I'd rather update the GUI from the EDT, doing something like this:
public void setIconLabel(final Icon icon) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
myLabel.setIcon(icon);
}
});
}

Related

Wait for long-running operation and show popup

Is it possible to wait for a method (say METHOD1) to finish, but if it is running longer than X secs, call another method until METHOD1 returns?
Some pseudocode:
method1();
startCountdown(1000); // time in millis
while (method1() still running) {
method2(); // shows a popup with spinner (Swing/AWT)
}
I guess, it must be done with concurrency, but I am not used to concurrent programming. So, I have no idea how to start.
The UI framework used is Swing/AWT.
So, the basic idea would be to use a combination of a SwingWorker and a Swing Timer.
The idea is if the Timer triggers before the SwingWorker is DONE, you execute some other workflow, otherwise you stop the Timer, for example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label;
private JButton startButton;
boolean hasCompleted = false;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
label = new JLabel("Waiting for you");
startButton = new JButton("Start");
add(label, gbc);
add(startButton, gbc);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
startWork();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void startWork() {
label.setText("Something wicked this way comes");
// You could build an isoloated workflow, which allowed you to pass
// three targets, the thing to be executed, the thing to be
// executed if time run over and the thing to be executed when
// the task completed (all via a single interface),
// but, you get the idea
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
label.setText("Wickedness is a bit slow today");
}
});
timer.setRepeats(false);
SomeLongRunningOperation worker = new SomeLongRunningOperation();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
label.setText("All is done");
startButton.setEnabled(true);
break;
}
}
});
worker.execute();
timer.start();
}
}
public class SomeLongRunningOperation extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(5000);
return null;
}
}
}
Play around with the timings to see what different effects you get.
Why use a SwingWorker? Because it has it's own state callbacks, which makes it easier to deal with
As I said in my comments, you could distill the workflow down into a re-usable concept, something like...
public class TimedTask<V> {
public static interface Task<V> {
public V execute() throws Exception;
}
public static interface TimedTaskListener<V> extends EventListener {
public void taskIsTakingLongThenExepected(TimedTask task);
public void taskDidComplete(TimedTask task, V value);
}
private Task<V> task;
private TimedTaskListener<V> listener;
private V value;
private int timeOut;
private Timer timer;
private SwingWorker<V, Void> worker;
private boolean hasCompleted = false;
public TimedTask(int timeOut, Task<V> task, TimedTaskListener<V> listener) {
this.task = task;
this.listener = listener;
this.timeOut = timeOut;
}
public V getValue() {
return value;
}
public int getTimeOut() {
return timeOut;
}
protected Task<V> getTask() {
return task;
}
protected TimedTaskListener<V> getListener() {
return listener;
}
public void execute() {
if (timer != null || worker != null) {
return;
}
hasCompleted = false;
worker = new SwingWorker<V, Void>() {
#Override
protected V doInBackground() throws Exception {
value = task.execute();
return value;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
getListener().taskDidComplete(TimedTask.this, value);
break;
}
}
});
timer = new Timer(getTimeOut(), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
getListener().taskIsTakingLongThenExepected(TimedTask.this);
}
});
timer.setRepeats(false);
worker.execute();
timer.start();
}
}
And then you could replace the startWork method in the first example with something like...
protected void startWork() {
label.setText("Something wicked this way comes");
TimedTask.Task<Void> task = new TimedTask.Task<Void>() {
#Override
public Void execute() throws Exception {
Thread.sleep(5000);
return null;
}
};
TimedTask<Void> timedTask = new TimedTask(2000, task, new TimedTask.TimedTaskListener<Void>() {
#Override
public void taskIsTakingLongThenExepected(TimedTask task) {
label.setText("Wickedness is taking it's sweet time");
}
#Override
public void taskDidComplete(TimedTask task, Void value) {
label.setText("Wickedness has arrived");
startButton.setEnabled(true);
}
});
timedTask.execute();
}
While SwingWorker is the appropriate tool for the job, for simple tasks you can get away with a Thread for the off-edt long task and a swing Timer to update the GUI:
import java.awt.*;
import javax.swing.*;
import javax.swing.Timer;
public class Main{
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
}
class TestPane extends JPanel{
private static Dimension size = new Dimension(250, 100);
private final JLabel label;
private final JButton start;
private int counter;
private Timer timer;
public TestPane() {
setLayout(new BorderLayout(10, 10));
label = new JLabel("Click START to run long process", JLabel.CENTER);
add(label,BorderLayout.NORTH);
start = new JButton("START");
start.addActionListener(e-> start() );
add(start, BorderLayout.SOUTH);
}
private void start() {
start.setEnabled(false);
int processRunTime = 10;
int updateTime = 1; //if this value >= processRunTime update() is not invoked
counter = 1;
simulateLongProcessOf(processRunTime);
timer = new Timer(1000*updateTime, e->update(counter++));
label.setText("Long process started");
timer.start();
}
private void stop() {
label.setText("Long process ended");
timer.stop();
start.setEnabled(true);
}
#Override
public Dimension preferredSize() {
return size;
}
private void simulateLongProcessOf(int seconds){
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000*seconds);
} catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
SwingUtilities.invokeLater(()->stop());
}
});
t1.start();
}
private void update(int count){
label.setText("Update # "+ count+" : long process is running" );
}
}
I've struggled with this question before.
What I ended up doing was, creating a separate class that extends AsyncTask. Added an interface/listener to this class that returned my object. Right before I start my AsyncTask, I'll disable buttons and put up a loading spinner. Once the AsyncTask comes back, I'll do my processing and reenable the buttons and take down the loading spinner. Of coarse I'm doing a rest call in the example, but it can be applied to anything that takes awhile. The reason why this is a better option than a while loop is that it's won't be burning cycles checking conditions.
public class RestCall extends AsyncTask {
private Context mContext;
private static final String TAG = "RestCall";
private AsyncResponse mListener;
public RestCall(Context context, URL url, AsyncResponse listener) {
this.mListener = listener;
this.mContext = context;
this.url = url;
}
public interface AsyncResponse {
void processFinish(JSONArray results);
}
#Override
protected Object doInBackground(Object[] objects) {
Log.d(TAG, "doInBackground: Thread: " + Thread.currentThread().getName());
return getResultsInJSONArray(url);
}
private JSONArray getResultsInJSONArray(URL url) {
//Here is where you will be doing the bulk of the work
//Doing a rest call and
//Processing results to JSONArray
}
#Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
Log.d(TAG, "onPostExecute: Handing off Object");
mListener.processFinish((JSONArray) o);
}
Now in your original class you'll add the following to your class:
public class myClass
private restCall call;
Than create a listener from that interface you made. Then pass the results to a method.
restCall.AsyncResponse listener = results -> handleResults(results);
With the listener setup you can you can execute your AsyncTask.
//here is were you would throw up the loading bar.
call = new restCall(this, url, listener);
call.execute();
private void handleResults(JSONArray results){
//process what you need to
//take down loading bar
}

Observer Pattern on MVC for specific fields

On the MVC pattern, which is the best option for the Model to notify the View (if this is the right approach in the first place) where, from all the fields of data the Model is storing, only a couple of them are updated. Specifically when we only want to update specific fields of the View.
I am currently using a MVC pattern with Observer/Subscriber (JAVA Swing) as described here: https://stackoverflow.com/a/6963529 but when the Model updates, it changes everything in the View when the update() funcion is called, it's impossible to determine which field from the Model changed in order to update only the required field in the View.
I read this topic: https://softwareengineering.stackexchange.com/a/359008 and this as well: https://stackoverflow.com/a/9815189 which I think it's usefull, but for the later, I can't understand very well how can I set a propertyChangeListener on a variale (int, float, etc). Also related to this: https://stackoverflow.com/a/9815189
The Main class where the software start to run:
public class Main {
public static void main(String[] args) {
Model m = new Model();
View v = new View(m);
Controller c = new Controller(m, v);
c.initController();
}
}
So the code that I have on Model is this:
public class Model extends Observable {
//...
private float speed;
private int batteryPercentage;
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
setChanged();
notifyObservers();
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
this.batteryPercentage = batteryPercentage;
setChanged();
notifyObservers();
}
}
The view knows the Model:
public class View implements Observer {
private Model model;
private JTextField txtFldSpeed;
private JTextField txtFldBattery;
private JFrame mainWindow;
public View(Model m) {
this.model = m;
initialize();
}
private void initialize() {
mainWindow = new JFrame();
mainWindow.setTitle("New Window");
mainWindow.setMinimumSize(new Dimension(1280, 720));
mainWindow.setBounds(100, 100, 1280, 720);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel tPanel1 = new JPanel();
tPanel1.setBorder(new LineBorder(new Color(0, 0, 0)));
tPanel1.setLayout(null);
mainWindow.getContentPane().add(tPanel1);
mainWindow.getContentPane().add(tPanel1);
txtFldSpeed = new JTextField();
txtFldSpeed.setEditable(false);
txtFldSpeed.setBounds(182, 11, 116, 22);
tPanel1.add(txtFldSpeed);
txtFldBattery = new JTextField();
txtFldBattery.setEditable(false);
txtFldBattery.setBounds(182, 43, 116, 22);
tPanel1.add(txtFldBattery);
mainWindow.setVisible(true);
}
#Override
public void update(Observable o, Object arg) {
txtFldSpeed.setText(Float.toString(model.getSpeed()) + " kn");
txtFldBattery.setText(Integer.toString(model.getBatteryPercentage()) + " %");
}
}
The Controller adds the View as a Observer of the Model:
public class Controller {
private Model model;
private View view;
public Controller(Model m, View v) {
this.model = m;
this.view = v;
}
public void initController() {
model.addObserver(view);
model.setSpeed(10);
}
}
What I am expecting is something that, when the Model is updated, let's say, function setSpeed() is called, the View is told that she needs to update itself on that specific field and not every "changable" field (like the txtFldBattery.
I want to do this because on the View, there are fields being updated a couple of times per second, and because I need to update everything on the view, a JComboBox which doesn't need to update that often, keeps closing when trying to select a option.
I would use SwingPropertyChangeSupport, make each of the model's state fields a "bound property" so that each state field can be listened to separately.
For instance, say you have a model that looked like this:
public class MvcModel {
public static final String SPEED = "speed";
public static final String BATTERY = "battery";
public static final int MAX_SPEED = 40;
private float speed;
private int batteryPercentage;
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
float oldValue = this.speed;
float newValue = speed;
this.speed = speed;
pcSupport.firePropertyChange(SPEED, oldValue, newValue);
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
int oldValue = this.batteryPercentage;
int newValue = batteryPercentage;
this.batteryPercentage = batteryPercentage;
pcSupport.firePropertyChange(BATTERY, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
}
Both the speed and the batteryPercent fields are "bound fields" in that any changes to these fields will trigger the property change support object to fire a notification message to any listeners that have registered with the support object, as reflected in the public void setXxxx(...) methods.
This way the controller could register listeners on the model for whatever properties it wants to listen to, and then notify the view of any changes. For example:
class SpeedListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
The set up could look something like:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class MVC2 {
private static void createAndShowGui() {
MvcModel model = new MvcModel();
MvcView view = new MvcView();
MvcController controller = new MvcController(model, view);
controller.init();
JFrame frame = new JFrame("MVC2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getMainDisplay());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MvcView {
private JPanel mainPanel = new JPanel();
private JSlider speedSlider = new JSlider(0, MvcModel.MAX_SPEED);
private JSlider batterySlider = new JSlider(0, 100);
private JProgressBar speedBar = new JProgressBar(0, MvcModel.MAX_SPEED);
private JProgressBar batteryPercentBar = new JProgressBar(0, 100);
public MvcView() {
speedSlider.setMajorTickSpacing(5);
speedSlider.setMinorTickSpacing(1);
speedSlider.setPaintTicks(true);
speedSlider.setPaintLabels(true);
speedSlider.setPaintTrack(true);
batterySlider.setMajorTickSpacing(20);
batterySlider.setMinorTickSpacing(5);
batterySlider.setPaintTicks(true);
batterySlider.setPaintLabels(true);
batterySlider.setPaintTrack(true);
speedBar.setStringPainted(true);
batteryPercentBar.setStringPainted(true);
JPanel inputPanel = new JPanel(new GridLayout(0, 1));
inputPanel.add(createTitledPanel("Speed", speedSlider));
inputPanel.add(createTitledPanel("Battery %", batterySlider));
JPanel displayPanel = new JPanel(new GridLayout(0, 1));
displayPanel.add(createTitledPanel("Speed", speedBar));
displayPanel.add(createTitledPanel("Battery %", batteryPercentBar));
mainPanel.setLayout(new GridLayout(1, 0));
mainPanel.add(createTitledPanel("Input", inputPanel));
mainPanel.add(createTitledPanel("Display", displayPanel));
}
private JComponent createTitledPanel(String title, JComponent component) {
JPanel titledPanel = new JPanel(new BorderLayout());
titledPanel.setBorder(BorderFactory.createTitledBorder(title));
titledPanel.add(component);
return titledPanel;
}
public JComponent getMainDisplay() {
return mainPanel;
}
public void setSpeed(float speed) {
speedBar.setValue((int) speed);
}
public void setBatteryPercent(int batteryPercent) {
batteryPercentBar.setValue(batteryPercent);
}
public JSlider getSpeedSlider() {
return speedSlider;
}
public JSlider getBatterySlider() {
return batterySlider;
}
}
class MvcController {
private MvcModel model;
private MvcView view;
public MvcController(MvcModel model, MvcView view) {
this.model = model;
this.view = view;
model.addPropertyChangeListener(MvcModel.BATTERY, new BatteryListener());
model.addPropertyChangeListener(MvcModel.SPEED, new SpeedListener());
view.getSpeedSlider().addChangeListener(chngEvent -> {
int value = view.getSpeedSlider().getValue();
model.setSpeed(value);
});
view.getBatterySlider().addChangeListener(chngEvent -> {
int value = view.getBatterySlider().getValue();
model.setBatteryPercentage(value);
});
}
public void init() {
view.getSpeedSlider().setValue(10);
view.getBatterySlider().setValue(100);
model.setSpeed(10);
model.setBatteryPercentage(100);
}
class SpeedListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
class BatteryListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
int batteryPercent = model.getBatteryPercentage();
view.setBatteryPercent(batteryPercent);
}
}
}
Side note: Observer and Observable have been deprecated in the most recent version of Java and so should their use should probably be avoided.
In your update method implementation you can determine with first argument o which Observable has changed and with second argument arg which value changed when you call: notifyObservers(this.speed);
Note that notifyObservers's signature accepts Object, and float primitive is not a subclass of Object.

Can you use publish from a method called in run in background?

I am using swingworker to run a method in the background and periodically update the gui with information, but from what I've found publish can't be called from another class. Here's where my Swingworker is called:
private void start() {
worker = new SwingWorker <Void, String>() {
#Override
protected Void doInBackground() throws Exception {
navigator.navigator();
return null;
}
#Override
protected void process(List<String> chunks) {
for (String line : chunks) {
txtrHello.append(line);
txtrHello.append("\n");
}
}
#Override
protected void done() {
}
};
worker.execute();
}
And now from the navigator method I want to call publish(String);, how would I do this? Moving all of my methods into doInBackground() would be impossible.
Possible solution is to add an observer to your Navigator object, the key being to somehow allow the Navigator to communicate with any listener (here the SwingWorker) that its state has changed:
Give Navigator a PropertyChangeSupport object as well as an addPropertyChangeListener(PropertyChangeListener listener) method that adds the passed in listener to the support object.
Give Navigator some type of "bound" property, a field that when its state is changed, often in a setXxxx(...) type method, notifies the support object of this change.
Then in your SwingWorker constructor, add a PropertyChangeListener to your Navigator object.
In this listener, call the publish method with the new data from your Navigator object.
For example:
import java.awt.event.ActionEvent;
import java.beans.*;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class PropChangeSupportEg extends JPanel {
private MyNavigator myNavigator = new MyNavigator();
private JTextField textField = new JTextField(10);
public PropChangeSupportEg() {
textField.setFocusable(false);
add(textField);
add(new JButton(new StartAction("Start")));
add(new JButton(new StopAction("Stop")));
}
private class StartAction extends AbstractAction {
public StartAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
if (myNavigator.isUpdatingText()) {
return; // it's already running
}
MyWorker worker = new MyWorker();
worker.execute();
}
}
private class StopAction extends AbstractAction {
public StopAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
myNavigator.stop();
}
}
private class MyWorker extends SwingWorker<Void, String> {
#Override
protected Void doInBackground() throws Exception {
if (myNavigator.isUpdatingText()) {
return null;
}
myNavigator.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (MyNavigator.BOUND_PROPERTY_TEXT.equals(evt.getPropertyName())) {
publish(evt.getNewValue().toString());
}
}
});
myNavigator.start();
return null;
}
#Override
protected void process(List<String> chunks) {
for (String chunk : chunks) {
textField.setText(chunk);
}
}
}
private static void createAndShowGui() {
PropChangeSupportEg mainPanel = new PropChangeSupportEg();
JFrame frame = new JFrame("Prop Change Eg");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class MyNavigator {
public static final String BOUND_PROPERTY_TEXT = "bound property text";
public static final String UPDATING_TEXT = "updating text";
private static final long SLEEP_TIME = 1000;
private PropertyChangeSupport pcSupport = new PropertyChangeSupport(this);
private String boundPropertyText = "";
private String[] textArray = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
private int textArrayIndex = 0;
private volatile boolean updatingText = false;
public void start() {
if (updatingText) {
return;
}
updatingText = true;
while (updatingText) {
textArrayIndex++;
textArrayIndex %= textArray.length;
setBoundPropertyText(textArray[textArrayIndex]);
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {}
}
}
public void stop() {
setUpdatingText(false);
}
public String getBoundPropertyText() {
return boundPropertyText;
}
public boolean isUpdatingText() {
return updatingText;
}
public void setUpdatingText(boolean updatingText) {
boolean oldValue = this.updatingText;
boolean newValue = updatingText;
this.updatingText = updatingText;
pcSupport.firePropertyChange(UPDATING_TEXT, oldValue, newValue);
}
public void setBoundPropertyText(String boundPropertyText) {
String oldValue = this.boundPropertyText;
String newValue = boundPropertyText;
this.boundPropertyText = boundPropertyText;
pcSupport.firePropertyChange(BOUND_PROPERTY_TEXT, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
}

JProgressBar doesn't update , can't find a clue

nice job , now i just wanna know why if i add into while loop the instruction System.out.println below the progress is shown on both , cmd and Pgbar in the Gui ?? :
while(progress < 99){
System.out.println("into while of PBar Thread progress = "+progress);
if(progress != Path.operationProgress){
operationProgressBar.setValue(progress);
progress = Path.operationProgress;
operationProgressBar.repaint(); } }
need some help around , i can't get the JProgressBar to update, i
can't use SwingWorker, i have to solve this without it . the variable
Path.operationProgress is a static variable from a "Path" class
instance, and it's updated from another thread, so i think the PBar
and Path instances are both executed in user's Threads and not in the
EDT . here is the Code of the progress bar :
import javax.swing.*;
public class Pbar extends Thread {
JProgressBar operationProgressBar;
public Pbar(JProgressBar operationProgressBar) {
this.operationProgressBar = operationProgressBar;
}
#Override
public void run() {
int progress = Path.operationProgress;
while(progress < 99) {
if(progress != Path.operationProgress) {
operationProgressBar.setValue(progress);
progress = Path.operationProgress;
operationProgressBar.repaint();
}}}
}
this is the action that launches the threads :
private javax.swing.JProgressBar operationProgressBar;
private javax.swing.JLabel pathImage;
private javax.swing.JButton simulatedAnnelingButton;
public class TSPGUI extends javax.swing.JFrame {
TSPMG tspInstance;
Path p, result;
String filename = "";
int neighborHood_Type = 1, i = 0;
// ......Constructor Stuff and init()
private void simulatedAnnelingButtonActionPerformed(java.awt.event.ActionEvent evt)
{
Thread sa = new Thread(){
#Override
public void run(){
result = p.SimulatedAnnealing(neighborHood_Type);
String lastCostString = result.Cost() + "";
lastCostLabel.setText(lastCostString);
}};
sa.start();
Pbar pb = new Pbar(operationProgressBar);
pb.start();
}
//Some other Stuff ...
}
If you can't use SwingWorker then use SwingUtilities.invokeLater, e.g.:
if (progress != Path.operationProgress) {
final int progressCopy = progress; // Probably not final so copy is needed
SwingUtilities.invokeLater(new Runnable() {
#Override
void run() {
operationsProgressBar.setValue(progressCopy);
}
});
}
Note: When doing this, everything used in run has to be final or there have to be other measures to access the variables. This code is symbolic in that regard.
You need to do operations on Swing components outside the event dispatching thread, there is no way around this.
I would use a PropertyChangeListener to allow you to make the annealing progress value a "bound" property of the class. Than any observer can follow this property if desired. For example:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
#SuppressWarnings("serial")
public class TspGui2 extends JPanel {
private static final String ANNEALING_PROGRESS = "Annealing Progress";
private JProgressBar progBar = new JProgressBar(0, 100);
private JLabel valueLabel = new JLabel();
private JButton beginAnnealingBtn = new JButton("Begin Annealing");
private MyAnnealing myAnnealing = new MyAnnealing(this);
public TspGui2() {
beginAnnealingBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
beginAnnealing();
}
});
myAnnealing.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(MyAnnealing.ANNEALING)) {
// be sure this is done on the EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int annealedValue = myAnnealing.getAnnealedValue();
setValue(annealedValue);
if (annealedValue >= MyAnnealing.MAX_ANNEALED_VALUE) {
beginAnnealingBtn.setEnabled(true);
}
}
});
}
}
});
progBar.setString(ANNEALING_PROGRESS);
progBar.setStringPainted(true);
JPanel northPanel = new JPanel(new GridLayout(1, 0));
northPanel.add(beginAnnealingBtn);
northPanel.add(valueLabel);
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(northPanel);
add(progBar);
}
public void setValue(int value) {
valueLabel.setText("Value:" + value);
progBar.setValue(value);
}
public void beginAnnealing() {
beginAnnealingBtn.setEnabled(false);
setValue(0);
myAnnealing.reset();
new Thread(new Runnable() {
public void run() {
myAnnealing.beginAnnealing();
}
}).start();
}
private static void createAndShowGui() {
TspGui2 mainPanel = new TspGui2();
JFrame frame = new JFrame("TspGui2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class MyAnnealing {
public static final String ANNEALING = "Annealing";
public static final int MAX_ANNEALED_VALUE = 100;
private SwingPropertyChangeSupport propChangeSupport =
new SwingPropertyChangeSupport(this);
private TspGui2 gui;
private int annealedValue;
public MyAnnealing(TspGui2 gui) {
this.gui = gui;
}
public void addPropertyChangeListener(
PropertyChangeListener listener) {
propChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(
PropertyChangeListener listener) {
propChangeSupport.removePropertyChangeListener(listener);
}
public void reset() {
setAnnealedValue(0);
}
// simulate some long process...
public void beginAnnealing() {
long sleepDelay = 100;
while (annealedValue < MAX_ANNEALED_VALUE) {
setAnnealedValue(annealedValue + 1);
try {
Thread.sleep(sleepDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getAnnealedValue() {
return annealedValue;
}
private void setAnnealedValue(int value) {
final int oldValue = this.annealedValue;
this.annealedValue = value;
propChangeSupport.firePropertyChange(ANNEALING, oldValue, annealedValue);
}
}

MVC Progress Bar Threading

I am using an MVC pattern for my design, when a user presses the search button, I call a search in the model, but I also want to update a progress bar with information returned from that model.
I have tried using a swingworker, but the progress bar does not update. I suspect I am doing something wrong with my threading.
My button as defined in the controller is:
class SearchBtnListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
_view.displayProgress();
}
}
This calls the search in the model and has the following call in the view:
public void displayProgress() {
TwoWorker task = new TwoWorker();
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent e) {
if ("progress".equals(e.getPropertyName())) {
_progressBar.setValue((Integer) e.getNewValue());
}
}
});
task.execute();
}
private class TwoWorker extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
_model.startSearch(getTerm()); // time intensive code
File file = new File("lock");
while (file.exists()){
setProgress(_model.getStatus());
System.out.println(_model.getStatus()); // never called
}
return null;
}
protected void done(){
updateMain();
}
}
Dummy function defined in Model for testing:
public int getStatus(){
Random r = new Random();
return r.nextInt();
}
Don't call
_progressBar.setValue(_model.getStatus());
from within your SwingWorker as this is calling Swing code from a background thread and is what the PropertyChangeListener is for anyway. Instead, just set the progress property, that's all.
Also, don't call done() from within the doInBackground method as this needs to be called from the EDT by the SwingWorker. So let the SwingWorker itself call this method when it is in fact done.
Also, Done() should be done() -- the first letter shouldn't be capitalized, and you should use #Override annotations in this code so you can be sure that you're overriding methods correctly.
Also, what does this do?
_model.startSearch(_view.getTerm());
Does it call code that takes a while to complete? Should this be initialized from within the SwingWorker doInBackground itself?
Edit:
Another option is to give the Model a bound int property, say called progress, and then add a PropertyChangeListener to it directly letting it update the JProgressBar. For example,
import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.*;
public class MVC_ProgressBarThread {
private static void createAndShowUI() {
MVC_View view = new MVC_View();
MVC_Model model = new MVC_Model();
MVC_Control control = new MVC_Control(view, model);
view.setControl(control);
JFrame frame = new JFrame("MVC_ProgressBarThread");
frame.getContentPane().add(view);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
#SuppressWarnings("serial")
class MVC_View extends JPanel {
private MVC_Control control;
private JProgressBar progressBar = new JProgressBar();
private JButton startActionButton = new JButton("Start Action");
public MVC_View() {
startActionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
buttonActionPerformed();
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(startActionButton);
setLayout(new BorderLayout());
add(buttonPanel, BorderLayout.NORTH);
add(progressBar, BorderLayout.CENTER);
}
public void setControl(MVC_Control control) {
this.control = control;
}
private void buttonActionPerformed() {
if (control != null) {
control.doButtonAction();
}
}
public void setProgress(int progress) {
progressBar.setValue(progress);
}
public void start() {
startActionButton.setEnabled(false);
}
public void done() {
startActionButton.setEnabled(true);
setProgress(100);
}
}
class MVC_Control {
private MVC_View view;
private MVC_Model model;
public MVC_Control(final MVC_View view, final MVC_Model model) {
this.view = view;
this.model = model;
model.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent pce) {
if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
view.setProgress((Integer)pce.getNewValue());
}
}
});
}
public void doButtonAction() {
view.start();
SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
model.reset();
model.startSearch();
return null;
}
#Override
protected void done() {
view.done();
}
};
swingworker.execute();
}
}
class MVC_Model {
public static final String PROGRESS = "progress";
private static final int MAX = 100;
private static final long SLEEP_DELAY = 100;
private int progress = 0;
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public void setProgress(int progress) {
int oldProgress = this.progress;
this.progress = progress;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS, oldProgress, progress);
pcs.firePropertyChange(evt);
}
public void reset() {
setProgress(0);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void startSearch() {
for (int i = 0; i < MAX; i++) {
int newValue = (100 * i) / MAX;
setProgress(newValue);
try {
Thread.sleep(SLEEP_DELAY);
} catch (InterruptedException e) {}
}
}
}

Categories