Java Observer/Observable pattern does not Notify - java

I am trying to build a simple Java application with SWT using the MVC pattern. I would like to be able to automatically update the view when something happens in the background so I am trying to use the Observer/Observable pattern, but it looks like my Observer is never notified when my Observable changes.
Here is the code:
Launcher.java (main class)
public class Launcher {
public static void main(String[] args) {
Application app = new Application();
PenguinView v = new PenguinView(app);
Controller api = new Controller(app, v);
}
}
Application.java (The background application)
public class Application {
private Penguin _myPenguin;
public Application() {
_myPenguin = new Penguin();
new Thread(_myPenguin).start();
}
public Penguin getPenguin() {
return _myPenguin;
}
}
Penguin.java (The model, my Observable)
public class Penguin extends Observable implements Runnable {
private String _color;
private boolean _isHappy;
private int _myRocks;
public Penguin() {
_color = "Black";
_isHappy = true;
_myRocks = 0;
}
public void paint(String color) {
_color = color;
System.out.println("My new color is " + _color);
setChanged();
notifyObservers();
}
public String getColor() {
return _color;
}
public void upset() {
_isHappy = false;
setChanged();
notifyObservers();
}
public void cheerUp() {
_isHappy = true;
setChanged();
notifyObservers();
}
public boolean isHappy() {
return _isHappy;
}
public void run() {
// Penguin starts walking and find rocks!
while(true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
iFoundRock();
}
}
private void iFoundRock() {
_myRocks++;
System.out.println("I now have " + _myRocks + " rocks");
setChanged();
notifyObservers();
}
}
PenguinView.java (The SWT View, my observer)
public class PenguinView implements Observer {
private Application _app;
private Display _d;
private Shell _s;
private Label _penguinColor;
private Label _penguinHumor;
private Label _penguinRocks;
private Button _upset;
private Button _cheerUp;
private Text _newColor;
private Button _paint;
public PenguinView(Application app) {
_app = app;
_d = new Display();
_s = new Shell();
RowLayout rl = new RowLayout();
rl.marginWidth = 12;
rl.marginHeight = 12;
_s.setLayout(rl);
new Label(_s, SWT.BORDER).setText("Penguin Color: ");
_penguinColor = new Label(_s, SWT.BORDER);
_penguinColor.setText(_app.getPenguin().getColor());
new Label(_s, SWT.BORDER).setText(" Humor: ");
_penguinHumor = new Label(_s, SWT.BORDER);
String humor = _app.getPenguin().isHappy() ? "Happy" : "Sad";
_penguinHumor.setText(humor);
new Label(_s, SWT.BORDER).setText(" Rocks: ");
_penguinRocks = new Label(_s, SWT.BORDER);
_penguinRocks.setText(String.valueOf(_app.getPenguin().howManyRocks()));
_upset = new Button(_s, SWT.PUSH);
_upset.setText(":(");
_upset.addListener(SWT.Selection, new Listener(){
public void handleEvent(Event e) {
_penguinHumor.setText("Sad");
}
});
_cheerUp = new Button(_s, SWT.PUSH);
_cheerUp.setText(":)");
_cheerUp.addListener(SWT.Selection, new Listener(){
public void handleEvent(Event e) {
_penguinHumor.setText("Happy");
}
});
_newColor = new Text(_s, SWT.BORDER);
_paint = new Button(_s, SWT.PUSH);
_paint.setText("Paint!");
_paint.addListener(SWT.Selection, new Listener(){
public void handleEvent(Event e) {
//_penguinColor.setText(_newColor.getText());
_app.getPenguin().paint(_newColor.getText());
}
});
_s.pack();
_s.open();
while (!_d.isDisposed()) {
if (!_d.readAndDispatch()) {
_d.sleep();
}
}
}
public void update(Observable obs, Object obj) {
System.out.println("I go here!");
_penguinRocks.setText(String.valueOf(((Penguin)obs).howManyRocks()));
if(obs.equals(_app.getPenguin())) {
_penguinRocks.setText(String.valueOf(((Penguin)obs).howManyRocks()));
}
}
public void update(Observable obs) {
System.out.println("I go there!");
}
Controller.java (The controller)
public class Controller {
Application _app;
PenguinView _v;
public Controller(Application app, PenguinView v) {
_app = app;
_v = v;
// Set observer
_app.getPenguin().addObserver(_v);
}
}
The output:
I now have 1 rocks
I now have 2 rocks
My new color is Blue
I now have 3 rocks
I now have 4 rocks
Do you have any idea of what I am doing wrong?
Thanks!

From what I can tell, the while loop in the PenguinView constructor is blocking your main thread, so the Controller never gets instantiated and the observer is never added.

Related

Stock Market Simulator Java GUI

I am making a stock market simulator and I keep getting an error from each of my 3 classes the first class MYOSM the error is:
"Cannot find symbol- class stock"
The second class MarketTable outputs:
Cannot find MarketDataModel
And the third class MarketDataModel outputs the error:
Cannot find symbol- class stock
Have I gone wrong somewhere in my code I have checked it multiple times and I can't seem to locate it.
Here is my code:
MYOSM class:
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class MYOSM extends JFrame implements Runnable {
Stock[] market = {
new Stock("JTree", 14.57),
new Stock("JTable", 17.44),
new Stock("JList", 16.44),
new Stock("JButton", 7.21),
new Stock("JComponent", 27.40)
};
boolean monitor;
Random rg = new Random();
Thread runner;
public MYOSM() {
// Not meant to be shown as a real frame
super("Thread only version . . .");
runner = new Thread(this);
runner.start();
}
public MYOSM(boolean monitorOn) {
super("Stock Market Monitor");
setSize(400, 100);
setDefaultCloseOperation(EXIT_ON_CLOSE);
monitor = monitorOn;
getContentPane().add(new JLabel("Trading is active. " +
"Close this window to close the market."),
BorderLayout.CENTER);
runner = new Thread(this);
runner.start();
}
public void run() {
while(true) {
int whichStock = Math.abs(rg.nextInt()) % market.length;
double delta = rg.nextDouble() - 0.4;
market[whichStock].update(delta);
if (monitor) {
market[whichStock].print();
}
try {
Thread.sleep(1000);
}
catch(InterruptedException ie) {
}
}
}
public Stock getQuote(int index) {
return market[index];
}
// This method returns the list of all the symbols in the market table
public String[] getSymbols() {
String[] symbols = new String[market.length];
for (int i = 0; i < market.length; i++) {
symbols[i] = market[i].symbol;
}
return symbols;
}
public static void main(String args[]) {
MYOSM myMarket = new MYOSM(args.length > 0);
myMarket.setVisible(true);
}
}
MarketTable class:
import java.awt.*;
import javax.swing.*;
public class MarketTable extends JFrame {
public MarketTable() {
super("Dynamic Data Test");
setSize(300, 200);
setDefaultCloseOperation(EXIT_ON_CLOSE);
MarketDataModel mdm = new MarketDataModel(5);
mdm.setStocks(new int[] { 0, 1, 2 });
JTable jt = new JTable(mdm);
JScrollPane jsp = new JScrollPane(jt);
getContentPane().add(jsp, BorderLayout.CENTER);
}
public static void main(String args[]) {
MarketTable mt = new MarketTable();
mt.setVisible(true);
}
}
MarketDataModel class:
import javax.swing.table.*;
import javax.swing.*;
public class MarketDataModel extends AbstractTableModel
implements Runnable {
Thread runner;
MYOSM market;
int delay;
public MarketDataModel(int initialDelay) {
market = new MYOSM();
delay = initialDelay * 1000;
Thread runner = new Thread(this);
runner.start();
}
Stock[] stocks = new Stock[0];
int[] stockIndices = new int[0];
String[] headers = {"Symbol", "Price", "Change", "Last updated"};
public int getRowCount() { return stocks.length; }
public int getColumnCount() { return headers.length; }
public String getColumnName(int c) { return headers[c]; }
public Object getValueAt(int r, int c) {
switch(c) {
case 0:
return stocks[r].symbol;
case 1:
return new Double(stocks[r].price);
case 2:
return new Double(stocks[r].delta);
case 3:
return stocks[r].lastUpdate;
}
throw new IllegalArgumentException("Bad cell (" + r + ", " + c +")");
}
public void setDelay(int seconds) { delay = seconds * 1000; }
public void setStocks(int[] indices) {
stockIndices = indices;
updateStocks();
fireTableDataChanged();
}
public void updateStocks() {
stocks = new Stock[stockIndices.length];
for (int i = 0; i < stocks.length; i++) {
stocks[i] = market.getQuote(stockIndices[i]);
}
}
public void run() {
while(true) {
updateStocks();
fireTableRowsUpdated(0, stocks.length - 1);
try { Thread.sleep(delay); }
catch(InterruptedException ie) {}
}
}
}
You're missing a Stock class there. Should be something like this I suppose:
public class Stock {
public Stock(String string, double d) {
this.symbol = string;
this.price = d;
}
public String symbol;
public double price;
public double delta;
public String lastUpdate;
public void print() {
System.out.println(this);
}
public void update(double delta2) {
this.delta = delta2;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Stock [symbol=").append(symbol).append(", price=").append(price).append(", delta=").append(delta).append(", lastUpdate=")
.append(lastUpdate).append("]");
return builder.toString();
}
}
Or is it just in a different package than those classes?
Also it's weird your error message has "stock" in lowercase.
With the Stock class added as above, I have managed to start your code, but I'm not sure what it was supposed to be doing. I must note it is poorly written in general, with some basic mistakes like usage of default package.

need TestNG run option in RCP application

I have have implementaed a plugin project and want to use TestNG tab for runner purpose in this application. I have a solution for JUnit but in TestNG still I am stuck. Please help out from this situation. Kindly find the JUnit configuration tab code in below:
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] {
new JUnitLaunchConfigurationTab(),
new JavaArgumentsTab(),
new JavaClasspathTab(),
new JavaJRETab(),
new SourceLookupTab(),
new EnvironmentTab(),
new CommonTab()
};
setTabs(tabs);
}
Please suggest.
****First create TestTab.java class by extend AbstractLaunchConfigurationTab******
private ILaunchConfigurationDialog fLaunchConfigurationDialog;
private final TestNGMainTab testngLaunchTab;
private Button runInUIThread;
/**
* Constructor to create a new junit test tab
*/
public TestTab() {
this.testngLaunchTab = new TestNGMainTab();
}
public void createControl(Composite parent) {
testngLaunchTab.createControl(parent);
Composite composite = (Composite) getControl();
createSpacer(composite);
createRunInUIThreadGroup(composite);
}
private void createRunInUIThreadGroup(Composite comp) {
runInUIThread = new Button(comp, SWT.CHECK);
runInUIThread.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
}
});
runInUIThread.setText(PDEUIMessages.PDEJUnitLaunchConfigurationTab_Run_Tests_In_UI_Thread);
GridDataFactory.fillDefaults().span(2, 0).grab(true, false).applyTo(runInUIThread);
}
private void createSpacer(Composite comp) {
Label label = new Label(comp, SWT.NONE);
GridDataFactory.fillDefaults().span(3, 0).applyTo(label);
}
public void initializeFrom(ILaunchConfiguration config) {
testngLaunchTab.initializeFrom(config);
updateRunInUIThreadGroup(config);
}
private void updateRunInUIThreadGroup(ILaunchConfiguration config) {
boolean shouldRunInUIThread = true;
try {
shouldRunInUIThread = config.getAttribute(IPDELauncherConstants.RUN_IN_UI_THREAD, true);
} catch (CoreException ce) {
}
runInUIThread.setSelection(shouldRunInUIThread);
}
public void performApply(ILaunchConfigurationWorkingCopy config) {
testngLaunchTab.performApply(config);
boolean selection = runInUIThread.getSelection();
config.setAttribute(IPDELauncherConstants.RUN_IN_UI_THREAD, selection);
}
#Override
public String getId() {
return IPDELauncherConstants.TAB_TEST_ID;
}
#Override
public void activated(ILaunchConfigurationWorkingCopy workingCopy) {
testngLaunchTab.activated(workingCopy);
}
#Override
public boolean canSave() {
return testngLaunchTab.canSave();
}
#Override
public void deactivated(ILaunchConfigurationWorkingCopy workingCopy) {
testngLaunchTab.deactivated(workingCopy);
}
#Override
public void dispose() {
testngLaunchTab.dispose();
}
#Override
public String getErrorMessage() {
return testngLaunchTab.getErrorMessage();
}
#Override
public Image getImage() {
return testngLaunchTab.getImage();
}
#Override
public String getMessage() {
return testngLaunchTab.getMessage();
}
public String getName() {
return testngLaunchTab.getName();
}
#Override
public boolean isValid(ILaunchConfiguration config) {
return testngLaunchTab.isValid(config);
}
public void setDefaults(ILaunchConfigurationWorkingCopy config) {
testngLaunchTab.setDefaults(config);
}
#Override
public void setLaunchConfigurationDialog(ILaunchConfigurationDialog dialog) {
testngLaunchTab.setLaunchConfigurationDialog(dialog);
this.fLaunchConfigurationDialog = dialog;
}
*** create Interface ITestNGPluginLauncherConstants*****
public interface ITestNGPluginLauncherConstants {
String LEGACY_UI_TEST_APPLICATION =
"org.testng.eclipse.runtime.legacytestapplication";
String NON_UI_THREAD_APPLICATION =
"org.testng.eclipse.runtime.nonuithreadtestapplication";
String UI_TEST_APPLICATION =
"org.testng.eclipse.runtime.uitestapplication";
String CORE_TEST_APPLICATION =
"org.testng.eclipse.runtime.coretestapplication";
String TestNGProgramBlock_headless = "No application [Headless]";
String TAB_PLUGIN_TESTNG_MAIN_ID =
"org.testng.eclipse.plugin.launch.tab.main";
}
****create class PluginTestNGMainTab.java*****
public class TestNGPluginTabGroup extends
AbstractLaunchConfigurationTabGroup {
public TestNGPluginTabGroup() {
}
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] {
new TestTab(),
new PluginTestNGMainTab(),
new JavaArgumentsTab(),
new PluginsTab(false),
new JavaArgumentsTab(),
new PluginsTab(),
new TracingTab(),
new ConfigurationTab(true),
new TracingTab(),
new EnvironmentTab(),
new CommonTab()
};
setTabs(tabs);
}
}
**** create class TestNGProgramBlock.java extends ProgramBlock*****
public TestNGProgramBlock(AbstractLauncherTab tab) {
super(tab);
}
public void setDefaults(ILaunchConfigurationWorkingCopy config) {
if (!LauncherUtils.requiresUI(config))
config.setAttribute(IPDELauncherConstants.APPLICATION,
ITestNGPluginLauncherConstants.CORE_TEST_APPLICATION);
else
super.setDefaults(config);
}
protected String[] getApplicationNames() {
TreeSet result = new TreeSet();
result.add(ITestNGPluginLauncherConstants.TestNGProgramBlock_headless);
String[] appNames = super.getApplicationNames();
for (int i = 0; i < appNames.length; i++) {
result.add(appNames[i]);
}
return appNames;
}
protected void initializeApplicationSection(ILaunchConfiguration config)
throws CoreException {
String application =
config.getAttribute(IPDELauncherConstants.APPLICATION, (String)null);
if(ITestNGPluginLauncherConstants.CORE_TEST_APPLICATION.
equals(application))
fApplicationCombo.setText(ITestNGPluginLauncherConstants.
TestNGProgramBlock_headless);
else
super.initializeApplicationSection(config);
}
protected void saveApplicationSection(ILaunchConfigurationWorkingCopy config)
{
if(fApplicationCombo.getText().equals(ITestNGPluginLauncherConstants.
TestNGPogramBlock_headless)){
String appName = fApplicationCombo.isEnabled() ?
ITestNGPluginLauncherConstants.CORE_TEST_APPLICATION : null;
config.setAttribute(IPDELauncherConstants.APPLICATION, appName);
config.setAttribute(IPDELauncherConstants.APP_TO_TEST, (String)null);
}
}
}

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);
}
}

Windows alignment in SWING-based GUI

My goal is to display several views of one object. For each view I create a thread. Also, I have a class which controls those views, e.g. send a command to align them. However, it is not always I get correct alignment. So there is a data races, and I cannot understand what I am doing wrong.
Here there is a piece of code showing the problem I have. It has a simple idea: create a main view window, and then align the second window of the same size near its right border.
First, I have an abstract class to create a thread:
public abstract class ViewWindow implements Runnable{
private Thread thread;
private boolean terminate = false ;
private Controller controller;
private UpdateTask currentUpdateTask = null;
private class UpdateTask {
boolean alignWindows = true;
}
public ViewWindow(Controller controller, String title) {
this.title = title;
this.controller = controller;
}
public void startThread() {
thread = new Thread(this);
thread.start();
}
#Override
public void run() {
UpdateTask updateTask = null;
synchronized (thread) {
while (terminate == false) {
try {
thread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
updateTask = currentUpdateTask;
currentUpdateTask = null;
if(updateTask.alignWindows) {
controller.getLock().lock();
setLocationRelativeTo(controller.getMainWindow());
controller.getLock().unlock();
}
}
}
}
public void alignWindowsUsingThread() {
synchronized (thread) {
currentUpdateTask = new UpdateTask();
thread.notify();
}
}
public abstract void setLocationRelativeTo(ImageViewWindow imageWindow);
}
Then I extend it to create an abstraction for the window views:
public abstract class ImageViewWindow extends ViewWindow {
private JFrame frame;
public ImageViewWindow(Controller controller, String title) {
super(controller, title);
frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel(title);
frame.getContentPane().add(label, BorderLayout.CENTER);
frame.setPreferredSize(new Dimension(300,500));
frame.pack();
frame.setVisible(true);
}
public JFrame getFrame() {
return frame;
}
synchronized public void setLocation(int x, int y) {
frame.setLocation(x, y);
}
synchronized public Point getLocation() {
return frame.getLocation();
}
}
Finally, I override a function to set relative location for each window:
public class FirstWindow extends ImageViewWindow {
public FirstWindow(Controller controller, String title) {
super(controller, title);
this.setLocation(50, 50);
this.startThread();
}
#Override
public void setLocationRelativeTo(ImageViewWindow imageWindow) { }
}
public class SecondWindow extends ImageViewWindow {
public SecondWindow(Controller controller, String title) {
super(controller, title);
this.startThread();
}
#Override
public void setLocationRelativeTo(ImageViewWindow imageWindow) {
Point location = imageWindow.getLocation();
int xOffSet = imageWindow.getFrame().getWidth();
int yOffSet = 0;
this.setLocation(xOffSet + location.x, yOffSet + location.y);
}
}
Here there is a class which is responsible for the control:
public class Controller {
private Lock controlLock;
private List<ImageViewWindow> windows = new ArrayList<ImageViewWindow>();
private ImageViewWindow mainWindow;
public Controller() {
controlLock = new ReentrantLock();
}
public ImageViewWindow getMainWindow() {
return mainWindow;
}
public Lock getLock() {
return controlLock;
}
public void addMainWindow(ImageViewWindow mainViewWindow) {
this.mainWindow = mainViewWindow;
this.addWindow(mainViewWindow);
}
public void addWindow(ImageViewWindow imageWindow) {
windows.add(imageWindow);
}
public void updateWindowPositions() {
for(ImageViewWindow window : windows) {
window.alignWindowsUsingThread();
}
}
}
And do run everything:
public class Start {
public static void main(String[] args) {
Controller controller = new Controller();
ImageViewWindow window1 = new FirstWindow(controller, "FirstWindow");
controller.addMainWindow(window1);
ImageViewWindow window2 = new SecondWindow(controller, "SecondWindow");
controller.addWindow(window2);
controller.updateWindowPositions();
}
}
UPD: I updated the code based on the answer below, but the problem still remains!
You don’t specify how you want to “align” your windows but from your code
public void setLocationRelativeTo(ImageViewWindow imageWindow) {
Point location = imageWindow.getLocation();
int xOffSet = this.getFrame().getWidth();
int yOffSet = 0;
this.setLocation(xOffSet + location.x, yOffSet + location.y);
}
I suppose you want this to be placed to the right of imageWindow. In this case you have to use imageWindow.x + imageWindow.width rather than imageWindow.x + this.width:
| imageWindow | this
x ← width → x ← width →
↳=imageWindow.x+imageWindow.width
So the correct method would be:
public void setLocationRelativeTo(ImageViewWindow imageWindow) {
Point location = imageWindow.getLocation();
int xOffSet = imageWindow.getFrame().getWidth();
int yOffSet = 0;
this.setLocation(xOffSet + location.x, yOffSet + location.y);
}
By the way, I don’t get why you are making such a simple task that complicated and even multi-threaded. There’s no benefit from multi-threading here, only a complication that obstructs the view on the simplest things…

Is MVC in Swing Thread Safe

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);
}
});
}

Categories