accessing variables and swing components through different threads - java

This question is related somewhat to the one i asked HERE.
Now, i have a class "Controller" which consists of the main method and all the swing components. there is a class named "VTOL" which consists of a variable named "altitude"(i have declared this variable volatile as of now).
here is a class that consists of a thread which runs in the background:
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* #author Vineet
*/
public class Gravity extends Thread {
String altStr;
double alt;
Controller ctrl = new Controller();
#Override
public void run() {
while (true) {
alt=VTOL.altitude;
System.out.println(alt);
alt = alt-0.01;
VTOL.altitude= (int) alt;
altStr=new Integer(VTOL.altitude).toString();
ctrl.lblAltitude.setText(altStr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Firstly, the problem i was facing initially was that i couldnt update value of "altitude" it remained 0 throughout the execution of program. So i declared it as volatile (I dont know if its a good practice)
Secondly, there is a jLabel in Controller class named "lblAltitude", i wish to update its value as its changed in this thread, but somehow thats not happening.
How can i do that?

A solution is to use a SwingPropertyChangeSupport object, to make altitude a "bound" property with this support object, to have your GUI listener to this model class and to thereby notify the GUI of changes in altitude.
e.g.,
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Gravity implements Runnable {
public static final String ALTITUDE = "altitude";
private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
private volatile double altitude;
#Override
public void run() {
while (true) {
double temp = altitude + 10;
setAltitude(temp); // fires the listeners
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
Double oldValue = this.altitude;
Double newValue = altitude;
this.altitude = newValue;
// this will be fired on the EDT since it is a SwingPropertyChangeSupport object
swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.removePropertyChangeListener(listener);
}
}
For a more complete runnable example:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class GravityTestGui extends JPanel {
private static final long ALT_SLEEP_TIME = 400;
private static final double ALT_DELTA = 5;
JLabel altitudeLabel = new JLabel(" ");
private Gravity gravity = new Gravity(ALT_SLEEP_TIME, ALT_DELTA);
public GravityTestGui() {
add(new JLabel("Altitude:"));
add(altitudeLabel);
gravity.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (Gravity.ALTITUDE.equals(pcEvt.getPropertyName())) {
String altText = String.valueOf(gravity.getAltitude());
altitudeLabel.setText(altText);
}
}
});
new Thread(gravity).start();
}
private static void createAndShowGui() {
GravityTestGui mainPanel = new GravityTestGui();
JFrame frame = new JFrame("GravityTest");
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 Gravity implements Runnable {
public static final String ALTITUDE = "altitude";
private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
private volatile double altitude;
private long sleepTime;
private double delta;
public Gravity(long sleepTime, double delta) {
this.sleepTime = sleepTime;
this.delta = delta;
}
#Override
public void run() {
while (true) {
double temp = altitude + delta;
setAltitude(temp); // fires the listeners
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
Double oldValue = this.altitude;
Double newValue = altitude;
this.altitude = newValue;
// this will be fired on the EDT since it is a SwingPropertyChangeSupport object
swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.removePropertyChangeListener(listener);
}
}

Whenever you modify a Swing component, you need to ensure that this event happens in the Event Dispatch Thread (i.e. EDT).

A third approach would be to have your Swing component know about the model, VTOL.
In Gravity, you'd update VTOL.altitude, then call repaint on the component. e.g.
while (true) {
VTOL.altitude -= 0.01;
VTOL.makeAnyOtherChangesHereAsWell();
controller.repaint();
// sleep, break etc. left as an exercise for the reader
}
Then, in the paintComponent() method (or maybe somewhere else in all the paint calls, there's a slight chance it needs to be elsewhere...) of Controller, which you know is running on the EDT
// update my widgets from the VTOL model - may want this in a method
String altStr=new Integer(VTOL.altitude).toString();
this.lblAltitude.setText(altStr);
// may be more, e.g. ...
this.lblFuelSupply.setText(VTOL.getFuelSupply());
super.paintComponent(); // now go draw stuff...
This is a bit tighter coupled than SwingPropertyChangeSupport, but the coupling is all between very related classes, so it is "reasonable", and in some ways this may be "clearer". And the Event Dispatch Queue will combine multiple repaints so this isn't as inefficient as it first appear. If multiple threads are updating stuff and queuing up multiple repaints(), only the last repaint() actually does anything.
A disadvantage is that if your GUI has a gazillion widgets and you update all of them every time this may get a bit slow. But processors are amazingly fast nowadays.

Related

Notify PropertyChangeListener faster

So I'm creating a JProgressBar that displays the progress of a CSV manipulation, where every line is read and checked if there are no null values in obligatory (NOT NULL) columns. For that, I've created a SwingWorker Task that handles converting the number of lines in the file to 100% on the maximum progress value, and adding up on the progress on the correct rate.
That's the SwingWorker:
public static class Task extends SwingWorker<String, Object> {
private int counter;
private double rate;
public Task(int max) {
// Adds the PropertyChangeListener to the ProgressBar
addPropertyChangeListener(
ViewHandler.getExportDialog().getProgressBar());
rate = (float)100/max;
setProgress(0);
counter = 0;
}
/** Increments the progress in 1 times the rate based on maximum */
public void step() {
counter++;
setProgress((int)Math.round(counter*rate));
}
#Override
public String doInBackground() throws IOException {
return null;
}
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
System.out.println("Progress done.");
}
}
My PropertyChangeListener, which is implemented by the JProgressBar wrapper:
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer) evt.getNewValue());
}
}
Then, where I actually use it, I override the doInBackground() method with the processing I need, calling step() on every iteration.
Task read = new Task(lines) {
#Override
public String doInBackground() throws IOException {
while(content.hasNextValue()) {
step();
// Processing
}
return output.toString();
}
};
read.execute();
return read.get();
So what is happening: the processing works and succeeds, then done() is called, and just after that the propertyChange() registers two 'state' events and one 'progress' event, setting the ProgressBar's progress from 0% to 100%.
What is happening What I thought was happening (check Hovercraft's answer for clarification) is described in the JavaDocs:
Because PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread multiple invocations to the setProgress method might occur before any PropertyChangeListeners are invoked. For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only.
So, after all that, my question is: am I doing something wrong? If not, is there a way for me to make the Event Dispatch Thread notify the PropertyChangeListeners as the onProgress() happens, or at least from time to time?
Obs.: the processing I'm testing takes from 3~5s.
Your problem is here:
read.execute();
return read.get();
get() is a blocking call, and so calling it from the event thread immediately after executing your worker will block the event thread and your GUI.
Instead, it should be called from a call-back method such as the done() method or from the property change listener after the worker has changed its state property to SwingWorker.StateValue.DONE.
For example
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestSwingWorkerGui extends JPanel {
private JProgressBar progressBar = new JProgressBar(0, 100);
private Action myAction = new MyAction("Do It!");
public TestSwingWorkerGui() {
progressBar.setStringPainted(true);
add(progressBar);
add(new JButton(myAction));
}
private class MyAction extends AbstractAction {
public MyAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
myAction.setEnabled(false);
Task read = new Task(30) {
#Override
public String doInBackground() throws Exception {
int counter = getCounter();
int max = getMax();
while (counter < max) {
counter = getCounter();
step();
TimeUnit.MILLISECONDS.sleep(200);
}
return "Worker is Done";
}
};
read.addPropertyChangeListener(new MyPropListener());
read.execute();
}
}
private class MyPropListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if ("progress".equals(name)) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer) evt.getNewValue());
} else if ("state".equals(name)) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
myAction.setEnabled(true);
#SuppressWarnings("unchecked")
SwingWorker<String, Void> worker = (SwingWorker<String, Void>) evt.getSource();
try {
String text = worker.get();
System.out.println("worker returns: " + text);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
private static void createAndShowGui() {
TestSwingWorkerGui mainPanel = new TestSwingWorkerGui();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class Task extends SwingWorker<String, Void> {
private int counter;
// private double rate;
private int max;
public Task(int max) {
// Adds the PropertyChangeListener to the ProgressBar
// addPropertyChangeListener(gui);
// !!rate = (float)100/max;
this.max = max;
setProgress(0);
counter = 0;
}
/** Increments the progress in 1 times the rate based on maximum */
public void step() {
counter++;
int progress = (100 * counter) / max;
progress = Math.min(100, progress);
setProgress(progress);
// setProgress((int)Math.round(counter*rate));
}
public int getCounter() {
return counter;
}
public int getMax() {
return max;
}
#Override
public String doInBackground() throws Exception {
return null;
}
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
System.out.println("Progress done.");
}
}

Keeping Timer in JPanel alive while program is running

I have a GUI created in a class called MainFrame. One of the JPanels of the GUI displays the current time and date, by second. When the user decides to use the GUI to analyze data, it invokes a class that processes data. When the data process is happening, the timer pauses, then resumes when the dataprocess is over. How can I have the timer continuously run even if the program is running? The timer is its own thread, but I do not understand where to start a thread for a JPanel.
Here are some code cut-outs
App.java (app to start the entire GUI)
public class App {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainFrame();
}
});
}
}
MainFrame (class that handles the JPanels and dataprocess impl)
public class MainFrame extends JFrame {
private DataProcess dataProcess = null;
...
...
private StatusPanel statusPanel;
...
...
public MainFrame() {
...
setJMenuBar(createFrameMenu());
initializeVariables();
constructLayout();
createFileChooser();
constructAppWindow();
}
private void initializeVariables() {
this.dataProcess = new DataProcess();
...
this.statusPanel = new StatusPanel();
...
}
private void constructLayout() {
JPanel layoutPanel = new JPanel();
layoutPanel.setLayout(new GridLayout(0, 3));
layoutPanel.add(dataControlsPanel());
setLayout(new BorderLayout());
add(layoutPanel, BorderLayout.CENTER);
add(statusPanel, BorderLayout.PAGE_END);
}
StatusPanel (panel that shows timer etc)
public class StatusPanel extends JPanel {
private static final long serialVersionUID = 1L;
private JLabel statusLabel;
private JLabel timeLabel;
private Timer timer;
public StatusPanel() {
initializeVariables();
constructLayout();
startTimer();
}
private void constructLayout() {
setLayout(new FlowLayout(FlowLayout.CENTER));
add(statusLabel);// , FlowLayout.CENTER
add(timeLabel);
}
public void startTimer() {
this.timer.start();
}
public void stopTimer() {
this.timer.setRunning(false);
}
private void initializeVariables() {
this.statusLabel = new JLabel();
this.timeLabel = new JLabel();
this.statusLabel.setText(StringConstants.STATUS_PANEL_TEXT);
this.timer = new Timer(timeLabel);
}
}
Timer.java (timer that is used in StatusPanel)
public class Timer extends Thread {
private boolean isRunning;
private JLabel timeLabel;
private SimpleDateFormat timeFormat;
public Timer(JLabel timeLabel) {
initializeVariables(timeLabel);
}
private void initializeVariables(JLabel timeLabel) {
this.timeLabel = timeLabel;
this.timeFormat = new SimpleDateFormat("HH:mm:ss dd-MM-yyyy");
this.isRunning = true;
}
#Override
public void run() {
while (isRunning) {
Calendar calendar = Calendar.getInstance();
Date currentTime = calendar.getTime();
timeLabel.setText(timeFormat.format(currentTime));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
}
Data process is done in the dataControlsPanel by use of actionlisteners.
When the user decides to use the GUI to analyze data, it invokes a class that processes data. When the data process is happening, the timer pauses, then resumes when the dataprocess is over. How can I have the timer continuously run even if the program is running
First of all, your timer should be a javax.swing.Timer or "Swing" Timer. This is built to work specifically on the Swing event thread and so should avoid many of the Swing threading problems that your current code shows -- for example, here: timeLabel.setText(timeFormat.format(currentTime)); -- this makes a Swing call from a background thread and is dangerous code. Next
The processing code should go into a SwingWorker. When the worker executes, you can pause the Swing Timer by calling stop() on the Timer, or simply let the timer to continue to run. When the SwingWorker has completed its action -- something I usually listen for with a PropertyChangeListener added to the SwingWorker, listening for its state property to change to SwingWorker.StateValue.DONE, call get() on the worker to extract any data it holds and more importantly to capture any exceptions that might be thrown.
For example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyApp extends JPanel {
// display the date/time
private static final String DATE_FORMAT = "HH:mm:ss dd-MM-yyyy";
private static final DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
// timer updates measures seconds, but updates every 0.2 sec's to be sure
private static final int TIMER_DELAY = 200;
// JLabel that shows the date/time
private JLabel timeLabel = new JLabel("", SwingConstants.CENTER);
// JButton's Action / listener. This starts long-running data processing
private Action dataProcessAction = new DataProcessAction("Process Data");
// the SwingWorker that the above Action executes:
private LongRunningSwProcess longRunningProcess;
// label to display the count coming from the process above
private JLabel countLabel = new JLabel("00");
public MyApp() {
// create a simple GUI
JPanel dataProcessingPanel = new JPanel();
dataProcessingPanel.add(new JButton(dataProcessAction)); // button that starts process
dataProcessingPanel.add(new JLabel("Count:"));
dataProcessingPanel.add(countLabel);
setLayout(new BorderLayout());
add(timeLabel, BorderLayout.PAGE_START);
add(dataProcessingPanel);
showTimeLabelCurrentTime();
// create and start Swing Timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
// display count from swing worker
public void setCount(int newValue) {
countLabel.setText(String.format("%02d", newValue));
}
// clean up code after SwingWorker finishes
public void longRunningProcessDone() {
// re-enable JButton's action
dataProcessAction.setEnabled(true);
if (longRunningProcess != null) {
try {
// handle any exceptions that might get thrown from the SW
longRunningProcess.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
// display the current time in our timeLabel JLabel
private void showTimeLabelCurrentTime() {
long currentTime = System.currentTimeMillis();
Date date = new Date(currentTime);
timeLabel.setText(dateFormat.format(date));
}
// Timer's ActionListener is simple -- display the current time in the timeLabel
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
showTimeLabelCurrentTime();
}
}
// JButton's action. This starts the long-running SwingWorker
private class DataProcessAction extends AbstractAction {
public DataProcessAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false); // first disable the button's action
countLabel.setText("00"); // reset count label
// then create SwingWorker and listen to its changes
longRunningProcess = new LongRunningSwProcess();
longRunningProcess.addPropertyChangeListener(new DataProcessListener());
// execute the swingworker
longRunningProcess.execute();
}
}
// listen for state changes in our SwingWorker
private class DataProcessListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(LongRunningSwProcess.COUNT)) {
setCount((int)evt.getNewValue());
} else if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
longRunningProcessDone();
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("My App");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MyApp());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// mock up of SwingWorker for long-running action
class LongRunningSwProcess extends SwingWorker<Void, Integer> {
public static final String COUNT = "count";
private static final int MIN_TIME_OUT = 5;
private static final int MAX_TIME_OUT = 10;
private int count = 0;
#Override
protected Void doInBackground() throws Exception {
// all this mock up does is increment a count field
// every second until timeOut reached
int timeOut = MIN_TIME_OUT + (int) (Math.random() * (MAX_TIME_OUT - MIN_TIME_OUT));
for (int i = 0; i < timeOut; i++) {
setCount(i);
TimeUnit.SECONDS.sleep(1);
}
return null;
}
// make count a "bounded" property -- one that will notify listeners if changed
public void setCount(int count) {
int oldValue = this.count;
int newValue = count;
this.count = newValue;
firePropertyChange(COUNT, oldValue, newValue);
}
public int getCount() {
return count;
}
}

Query on creating separate thread in java?

Below is the compiled program replica of actual problem code,
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class Dummy {
public static boolean getUserCheck(int size, boolean Check) {
if (Check) {
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
if (ret > 0) {
System.out.println("User said No: " + ret);
return false;
} else if (ret <= 0) {
System.out.println("user said Yes: " + ret);
return true;
}
}
return true;
}
public static void workerMethod1() {
System.out.println("am worker method 1");
}
public static void workerMethod2() {
System.out.println("am worker method 2");
}
public static void main(String[] args) {
System.out.println("mainthread code line 1");
int size = 13;
boolean thresholdBreach = true;
if (getUserCheck(size, thresholdBreach)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod1();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod2();
}
});
}
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
}
}
where i would like to run the if{} block in main() on separate thread. Because these 2 lines,
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
need not wait for completion of if(){} block
Another problem is, experts recommend to run confirm-dialog methods on event thread.
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
Please help me!!!!
JOptionPane is a Swing method and should be called on the EDT, the Event Dispatch Thread, and only on this thread, and so it suggests that all your code above should be on the EDT, and that most of your SwingUtilities.invokeLater(new Runnable() calls are completely unnecessary. The only necessary ones will be the main one, where you launch your Swing GUI code, and any areas where Swing calls need to be made from within background threads. Again, if any of the above code is being made within background threads, then the JOptionPane should not be in that thread.
For more specific information in this or any other answer, please provide more specific information in your question. Let's end all confusion. The best way to get us to fully and quickly understand your problem would be if you were to to create and post a minimal example program, a small but complete program that only has necessary code to demonstrate your problem, that we can copy, paste, compile and run without modification.
I have a sneaking suspicion that a decent refactoring along MVC lines could solve most of your problems. Your code is very linear with its lines of code that must follow one another and its if blocks, and it is also tightly coupled with your GUI, two red flags for me. Perhaps better would be less linear code, more event and state-driven code, code where your background code interacts with the GUI via observer notification, and where the background code likewise responds to state changes in the GUI from control notification.
Your control needs two SwingWorkers, one to get the row count and the other to get the rest of the data if the user decides to do so. I'd add a PropertyChangeListener to the first SwingWorker to be notified when the row count data is ready, and then once it is, present it to the view for the user to select whether or not to proceed. If he decides to proceed, I'd then call the 2nd SwingWorker to get the main body of the data.
For example, a rough sketch of what I'm talking about:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerFooView extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 300;
private JProgressBar progressBar;
private JDialog dialog;
public SwingWorkerFooView() {
add(new JButton(new ButtonAction("Foo", this)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public boolean showOptionGetAllData(int numberOfRows) {
String message = "Number of rows = " + numberOfRows + ". Get all of the data?";
String title = "Get All Of Data?";
int optionType = JOptionPane.YES_NO_OPTION;
int result = JOptionPane.showConfirmDialog(this, message, title, optionType);
return result == JOptionPane.YES_OPTION;
}
public void showProgressBarDialog() {
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
Window window = SwingUtilities.getWindowAncestor(this);
dialog = new JDialog(window, "Hang on", ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel();
panel.add(progressBar);
dialog.add(panel);
dialog.pack();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
public void closeProgressBarDialog() {
dialog.dispose();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SwingWorkerFoo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SwingWorkerFooView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ButtonAction extends AbstractAction {
Workers workers = new Workers();
private SwingWorker<Integer, Void> firstWorker;
private SwingWorker<List<String>, Void> secondWorker;
private SwingWorkerFooView mainGui;
public ButtonAction(String name, SwingWorkerFooView mainGui) {
super(name);
this.mainGui = mainGui;
}
#Override
public void actionPerformed(ActionEvent e) {
firstWorker = workers.createFirstWorker();
firstWorker.addPropertyChangeListener(new FirstPropertyChangeListener());
firstWorker.execute();
mainGui.showProgressBarDialog();
}
private class FirstPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
int numberOfRows = firstWorker.get();
boolean getAllData = mainGui.showOptionGetAllData(numberOfRows);
if (getAllData) {
secondWorker = workers.createSecondWorker();
secondWorker.addPropertyChangeListener(new SecondPropertyChangeListener());
secondWorker.execute();
mainGui.showProgressBarDialog();
} else {
// user decided not to get all data
workers.cleanUp();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
private class SecondPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
List<String> finalData = secondWorker.get();
// display finalData in the GUI
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
class Workers {
// database object that may be shared by two SwingWorkers
private Object someDataBaseVariable;
private Random random = new Random(); // just for simulation purposes
private class FirstWorker extends SwingWorker<Integer, Void> {
#Override
protected Integer doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
// here we create our database object and check how many rows there are
int rows = random.nextInt(10 + 10); // this is just for demonstration purposes only
// here we create any objects that must be shared by both SwingWorkers
// and they will be saved in a field of Workers
someDataBaseVariable = "Fubar";
return rows;
}
}
private class SecondWorker extends SwingWorker<List<String>, Void> {
#Override
protected List<String> doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
List<String> myList = new ArrayList<>();
// here we go through the database filling the myList collection
return myList;
}
}
public SwingWorker<Integer, Void> createFirstWorker() {
return new FirstWorker();
}
public void cleanUp() {
// TODO clean up any resources and database stuff that will not be used.
}
public SwingWorker<List<String>, Void> createSecondWorker() {
return new SecondWorker();
}
}
The key to all of this is to not to think in a linear console program way but rather to use observer design pattern, i.e., listeners of some sort to check for change of state of both the GUI and the model.
It's essentially:
create worker
add observer to worker (property change listener)
execute worker
show progress bar dialog or notify user in some way that worker is executing.
The listener will be notified when the worker is done, and then you can query the worker (here via the get() method call) as to its end result.
Then the progress dialog can be closed
And the view can display the result or get additional information from the user.
Yes; SwingUtilities.invokeLater() simply places your runnable on the AWT event queue to be processed later, and it is safe to do so at any time.

Access static variable from another class

I have two classes in same package. i have declared a static variable in one class and want to access that variable in another class.
Here is my code in which i have declared the static variable
public class wampusGUI extends javax.swing.JFrame {
static String userCommand;
public wampusGUI() {
initComponents();
}
public void setTextArea(String text) {
displayTextArea.append(text);
}
private void enterButtonActionPerformed(java.awt.event.ActionEvent evt) {
userCommand = commandText.getText();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
wampusGUI w = new wampusGUI();
w.setVisible(true);
Game g = new Game(w);
g.play();
}
});
}
}
Here is the code in which i want to access variable
public class Game {
private wampusGUI gui;
public Game(wampusGUI w) {
world = new World();
world.start();
gui = w;
}
public void play() {
gui.setTextArea(welcome());
gui.setTextArea(describe());
for (;;) {
String s = userCommand; // here value should come should
System.out.println(userCommand);
Command c = Command.create(s);
String r = c.perform(world);
// is game over?
if (r == null) {
break;
}
System.out.println(r);
}
System.out.println("Game over");
}
}
However, i can pass the variable from first class as a argument. but the problem is that, when i will run program the value is going null first time, which i dont want. i want when i enter value in textfield then it should go to another class.
Thank you.
Looking at your code, it seems you want to show dialogs to your user with a certain text
gui.setTextArea(welcome());
gui.setTextArea(describe());
and sometimes, that dialog should capture user input which is handled afterwards.
Those setTextArea calls are not what you want to use. The user will never see the welcome message as it will immediately be replaced by the describe message.
Make sure you do not block the Event Dispatch Thread (EDT) or nothing will be shown at all. I do not know what your Command class will do, but I see an infinite loop on the Event Dispatch Thread which is never a good thing. Take a look at the Concurrency in Swing tutorial for more information
Thanks to that for loop, the user will simply not be capable to input any command as the EDT is busy handling your loop. What you need is a blocking call allowing the user to provide input (not blocking the EDT, but just blocking the execution of your code). The static methods in the JOptionPane class are perfectly suited for this (e.g. the JOptionPane#showInputDialog). These methods also have a mechanism to pass the user input back to the calling code without any static variables, which solves your problem.
I suggest that you use a listener of one sort or another to allow the Game object to listen for and respond to changes in the state of the GUI object. There are several ways to do this, but one of the most elegant and useful I've found is to use Swing's own innate PropertyChangeSupport to allow you to use PropertyChangeListeners. All Swing components will allow you to add a PropertyChangeListener to it. And so I suggest that you do this, that you have Game add one to your WampusGUI class (which should be capitalized) object like so:
public Game(WampusGUI w) {
gui = w;
gui.addPropertyChangeListener(new PropertyChangeListener() {
// ....
}
This will allow Game to listen for changes in the gui's state.
You'll then want to make the gui's userCommand String a "bound property" which means giving it a setter method that will fire the property change support notifying all listeners of change. I would do this like so:
public class WampusGUI extends JFrame {
public static final String USER_COMMAND = "user command";
// ....
private void setUserCommand(String userCommand) {
String oldValue = this.userCommand;
String newValue = userCommand;
this.userCommand = userCommand;
firePropertyChange(USER_COMMAND, oldValue, newValue);
}
Then you would only change this String's value via this setter method:
private void enterButtonActionPerformed(java.awt.event.ActionEvent evt) {
setUserCommand(commandText.getText());
}
The Game's property change listener would then respond like so:
gui.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
// is the property being changed the one we're interested in?
if (WampusGUI.USER_COMMAND.equals(pcEvt.getPropertyName())) {
// get user command:
String userCommand = pcEvt.getNewValue().toString();
// then we can do with it what we want
play(userCommand);
}
}
});
One of the beauties of this technique is that the observed class, the GUI, doesn't have to have any knowledge about the observer class (the Game). A small runnable example of this is like so:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class WampusGUI extends JFrame {
public static final String USER_COMMAND = "user command";
private String userCommand;
private JTextArea displayTextArea = new JTextArea(10, 30);
private JTextField commandText = new JTextField(10);
public WampusGUI() {
initComponents();
}
private void setUserCommand(String userCommand) {
String oldValue = this.userCommand;
String newValue = userCommand;
this.userCommand = userCommand;
firePropertyChange(USER_COMMAND, oldValue, newValue);
}
private void initComponents() {
displayTextArea.setEditable(false);
displayTextArea.setFocusable(false);
JButton enterButton = new JButton("Enter Command");
enterButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
enterButtonActionPerformed(evt);
}
});
JPanel commandPanel = new JPanel();
commandPanel.add(commandText);
commandPanel.add(Box.createHorizontalStrut(15));
commandPanel.add(enterButton);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
mainPanel.add(new JScrollPane(displayTextArea));
mainPanel.add(commandPanel, BorderLayout.SOUTH);
add(mainPanel);
}
public void setTextArea(String text) {
displayTextArea.append(text);
}
private void enterButtonActionPerformed(java.awt.event.ActionEvent evt) {
setUserCommand(commandText.getText());
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
WampusGUI w = new WampusGUI();
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
w.pack();
w.setLocationRelativeTo(null);
w.setVisible(true);
Game g = new Game(w);
g.play();
}
});
}
}
class Game {
private WampusGUI gui;
public Game(WampusGUI w) {
gui = w;
gui.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
// is the property being changed the one we're interested in?
if (WampusGUI.USER_COMMAND.equals(pcEvt.getPropertyName())) {
// get user command:
String userCommand = pcEvt.getNewValue().toString();
// then we can do with it what we want
play(userCommand);
}
}
});
}
public void play() {
gui.setTextArea("Welcome!\n");
gui.setTextArea("Please enjoy the game!\n");
}
public void play(String userCommand) {
// here we can do what we want with the String. For instance we can display it in the gui:
gui.setTextArea("User entered: " + userCommand + "\n");
}
}
I agree with Jon Skeet that this is not a good solution...
But in case u want an dirty solution to ur problem then u can try this:
public class wampusGUI extends javax.swing.JFrame
{
private static wampusGUI myInstance;
public wampusGUI( )
{
myInstance = this;
initComponents();
}
public static void getUserCommand()
{
if(myInstance!=null)
{
return myInstance.commandText.getText();
}
else
{
return null;
}
}
......
......
}
in the other class use:
public void play()
{
.....
//String s = userCommand; // here value should come should
String s = wampusGUI.getUserCommand();
.....
}
This kind of code is there in some of our legacy projects... and I hate this.

Java notSerializableExceptionError

When trying to save an arraylist of my class Click, I get this error: java.io.NotSerializableException:javax.swing.text.DefaultHighlighter$LayeredHighlightInfo
on this line of code: os.writeObject(saveList);. Even though I made my Click class implement serializable. Does anyone know the cause of this?
Here is my save Method:
public static void saveArray(ArrayList<Click> saveList) {
JFileChooser c = new JFileChooser();
c.showSaveDialog(new JFrame());
File f = c.getSelectedFile();
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(
f.getAbsolutePath()));
os.writeObject(saveList);
} catch (IOException e1) {
e1.printStackTrace();
}
}
And here is my Click class:
public static class Click implements Serializable {
JTextField xClickField;
JTextField yClickField;
JTextField clickIntervalField;
JTextField repeatTimesField;
boolean isLeft;
Integer clickX;
Integer clickY;
Integer clickInterval;
Integer clickTimes;
public Click(boolean left){
xClickField = new JTextField();
yClickField = new JTextField();
clickIntervalField = new JTextField();
repeatTimesField = new JTextField();
clickX = 0;
clickY = 0;
clickInterval = 0;
clickTimes = 0;
isLeft = left;
addToJPanel();
}
public void addToJPanel() {
xClickField.setText(clickX.toString());
yClickField.setText(clickY.toString());
clickIntervalField.setText(clickInterval.toString());
repeatTimesField.setText(clickTimes.toString());
panel.add(xClickField);
panel.add(yClickField);
panel.add(clickIntervalField);
panel.add(repeatTimesField);
frame.setVisible(false);
frame.setVisible(true);
}
public void removeFromJPanel() {
panel.remove(xClickField);
panel.remove(yClickField);
panel.remove(clickIntervalField);
panel.remove(repeatTimesField);
frame.setVisible(false);
frame.setVisible(true);
}
}
By the way I took out a chunk of code from the Click class. So if you think that the error could be in that portion of the code, I will gladly add it in.
Thanks in advance!
Implementing Serializable is not sufficient to make an object serializable. For example, a Socket is not serializable: it doesn't make sense to serialize a socket. So, if you have a Foo class that has a field of type Socket and that implements Serializable, how do you intend to serialize a Foo instance. It won't work. All the fields of a serializable object msut also be serializable, recursively.
And, as Hovercraft says in his comment, you should serialize data, not swing components.
You're serializing JTextFields and other Swing components which is a waste of time and resources and is completely unnecessary. You should be serializing the state of your GUI, the data held by the class's fields. If you understand MVC, you should be serializing the model, not the view. If you don't understand MVC, Google it or read this article and learn the key concepts as they are key to creating GUI programs in any language.
Also, for my money, I'd use JAXB or some other XML-based tool to serialize your data as it is saved in text format and thus understandable when read.
Example of separating GUI from model and using a property change listener to listen and respond to property changes:
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class SimpleClickEg {
private static void createAndShowGui() {
SimpleClickPanel clickPanel = new SimpleClickPanel();
JFrame frame = new JFrame("SimpleClickEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(clickPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class SimpleClickPanel extends JPanel {
private static final int PREF_WIDTH = 800;
private static final int PREF_HIEGHT = 600;
private JTextField clickCountField = new JTextField(5);
private JTextField clickXField = new JTextField(5);
private JTextField clickYField = new JTextField(5);
private SimpleClick click = new SimpleClick();
public SimpleClickPanel() {
add(new JLabel("Click X:"));
add(clickXField);
add(new JLabel("Click Y:"));
add(clickYField);
add(new JLabel("Click Count:"));
add(clickCountField);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
click.setClickPoint(e.getPoint());
}
});
click.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (SimpleClick.CLICK_COUNT.equals(evt.getPropertyName())) {
clickCountField.setText(String.valueOf(click.getClickCount()));
} else if (SimpleClick.CLICK_X.equals(evt.getPropertyName())) {
clickXField.setText(String.valueOf(click.getClickX()));
} else if (SimpleClick.CLICK_Y.equals(evt.getPropertyName())) {
clickYField.setText(String.valueOf(click.getClickY()));
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_WIDTH, PREF_HIEGHT);
}
public SimpleClick getClick() {
return click;
}
}
class SimpleClick implements Serializable {
private static final long serialVersionUID = 1L;
public static final String CLICK_COUNT = "click count";
public static final String CLICK_X = "click x";
public static final String CLICK_Y = "click y";
private int clickCount;
private int clickX;
private int clickY;
private transient SwingPropertyChangeSupport spcSupport = new SwingPropertyChangeSupport(
this);
public int getClickCount() {
return clickCount;
}
public void setClickCount(int clickCount) {
Integer oldValue = this.clickCount;
Integer newValue = clickCount;
this.clickCount = newValue;
spcSupport.firePropertyChange(CLICK_COUNT, oldValue, newValue);
}
public void incrementClickCount() {
setClickCount(getClickCount() + 1);
}
public void setClickPoint(Point p) {
setClickX(p.x);
setClickY(p.y);
incrementClickCount();
}
public int getClickX() {
return clickX;
}
public void setClickX(int clickX) {
Integer oldValue = this.clickX;
Integer newValue = clickX;
this.clickX = newValue;
spcSupport.firePropertyChange(CLICK_X, oldValue, newValue);
}
public int getClickY() {
return clickY;
}
public void setClickY(int clickY) {
Integer oldValue = this.clickY;
Integer newValue = clickY;
this.clickY = newValue;
spcSupport.firePropertyChange(CLICK_Y, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
spcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
spcSupport.removePropertyChangeListener(listener);
}
}
As you can see the error clearly states that javax.swing.text.DefaultHighlighter is not serializable.
Now this class is used by composition inside the JTextField, which is a GUI component and it is not meant to be serialized. From your code it seems that you don't need to serialize the fields themselves, so just mark them as transient and you are done.
As a side note: it is always good to split what is your data from what is your GUI so that you can easily serialize just data and foget about anything concerning the GUI. This helps in general, not just in serialization, to preserve encapsulation and use OOP as it is meant to be used.
The problem is that your Click class has references to JTextField instances, and these (presumably) have references to some Swing class called DefaultHighlighter.LayeredHighlightInfo ... and that is not serializable.
You probably need to declare the 4 JTextField variables as transient. As a general rule, Java GUI classes such as Swing components are not effectively serializable.

Categories