SwingWorker setting a flag after finishing its work - java

I have put together an application that opens text files and allows users to edit them (eg: text editor)
Some text files can be arbitrarily large, so it would take some time to open them. I have added a progress bar to inform the user that stuff is actually happening, and am using a swing worker to perform the actual file loading, giving it a reference to a text area to dump all the text.
I also have a flag in the main application called isFileLoaded which is true if there's a file open, and false otherwise. Ideally, the swing worker should set that value after it finishes loading the file and doing any processing that it needs to do.
I have written the swing worker as a separate class, so it's not nested inside my main Frame class that holds all of the GUI logic, mainly because I do not like to define classes inside classes purely for aesthetic reasons. As such, I am currently passing a reference to the entire Frame to the swing worker and letting it set the value of the flag.
Is this a good way to do things? Are there better ways?

Consider rather adding a PropertyChangeListener which holds a reference to your Frame (an anonymous inner-class would be just fine for that matter) and which listens to the "state" property. The value of the event will be equal to StateValue.DONE when the SwingWorker has finished.
Here is a fully working 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.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.SwingWorker.StateValue;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestSwingWorker {
private JProgressBar progressBar;
protected void initUI() {
final JFrame frame = new JFrame();
frame.setTitle(TestSwingWorker.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("Clik me to start work");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
doWork();
}
});
progressBar = new JProgressBar(0, 100);
frame.add(progressBar, BorderLayout.NORTH);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private boolean someFlag;
protected void doWork() {
SwingWorker<Void, Integer> worker = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < 100; i++) {
// Simulates work
Thread.sleep(10);
publish(i);
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
progressBar.setValue(chunks.get(chunks.size() - 1));
}
#Override
protected void done() {
progressBar.setValue(100);
progressBar.setStringPainted(true);
progressBar.setString("Done");
}
};
worker.getPropertyChangeSupport().addPropertyChangeListener("state", new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (StateValue.DONE.equals(evt.getNewValue())) {
someFlag = true;
}
}
});
worker.execute();
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestSwingWorker().initUI();
}
});
}
}

You should restructure your code a little to avoid using the whole Frame which indeed is not really clean (but if it works who cares).
If you want to be more cool from a design point of view you should use a model:
class FileModel
{
boolean isLoading;
// getter and setter that notifies
}
and pass only this model to your worker, and once done set the flag.

Related

How to update Swing GUI in realtime - JAVA

I am writing a java program to get realtime stock data. I used the URL class to get the information from yahoo finance. I am able to get all the information I need using a class for the stock. However, I want the GUI to update the value of the stock price every second or half second by calling a method that get the price of the stock, and update the value in a JLabel every second. So essentially, how do I update a value in Swing GUI by calling a method every second? Thank you.
There are a number of ways you might achieve this. A Swing Timer is a good choice to generate calls backs (into the Event Dispatching Thread) after a specified delay.
But what you probably want to is schedule a callback to perform the request and once it's completed, update the UI and schedule another callback. The point been, the actual request might take longer to call/process than the amount of time you've allocated between calls.
The following makes use of SwingWorker to perform the actual "work", using a random delay of between 1-5 seconds, once completed, it notifies a "observer" of some new state (in this case, just some nice messages).
Once the worker completes, a new worker is scheduled, using a ScheduledExecutorService (although you could do it manually with a Swing Timer)
See Concurrency in Swing
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
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 ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
private JLabel label;
public TestPane() {
setBorder(new EmptyBorder(16, 16, 16, 16));
label = new JLabel("Nothing happening here, just waiting for stuff");
setLayout(new GridBagLayout());
add(label);
startNextWorker();
}
protected void startNextWorker() {
ExecutorWorker worker = new ExecutorWorker(new ExecutorWorker.Observer() {
#Override
public void workerDidUpdate(String message) {
label.setText(message);
}
});
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (worker.getState() == SwingWorker.StateValue.DONE) {
worker.removePropertyChangeListener(this);
startNextWorker();
}
}
});
service.schedule(worker, 1500, TimeUnit.MILLISECONDS);
}
}
public class ExecutorWorker extends SwingWorker<Void, String> {
public interface Observer {
public void workerDidUpdate(String message);
}
private Random rnd = new Random();
private Observer observer;
public ExecutorWorker(Observer observer) {
this.observer = observer;
}
#Override
protected Void doInBackground() throws Exception {
publish("Starting some new work, back in a seocnd");
Thread.sleep(1000 + rnd.nextInt(5000));
publish("All the work is now done");
return null;
}
#Override
protected void process(List<String> chunks) {
for (String messages : chunks) {
observer.workerDidUpdate(messages);
}
}
}
}

Inheritence of a JButton Java - Overriding Method Issue

I am wanting to set the visibility of a JButton to false in another class. So what I am doing is overriding the boolean function I created in StudentAccount named getWriteBtnVisibility() to change the visibility of the button in the HW class. So basically I want to make the JButton invisible in the StudentAccount. Since I want that button to be visible when a different type of account is logged in.
However, the way I am doing it seems not to be working. I have debugged my code and not understanding why it is not overriding the function. If I can get some guidance, I'd greatly appreciate it.
StudentAccount:
import java.awt.EventQueue;
public class StudentAccount extends AccountTemplate {
/**
* Launch the application.
*/
#Override
public String getHomeworkBtnName() {
return "Submit Assignment";
}
#Override
public boolean getWriteBtnVisibility() {
return false;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
StudentAccount window = new StudentAccount();
window.frmAccountTemplate.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
AccountTemplate:
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.Font;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class AccountTemplate extends HW {
protected JFrame frmAccountTemplate;
/**
* Launch the application.
*/
public String getHomeworkBtnName() {
return "Hw";
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
AccountTemplate window = new AccountTemplate();
window.frmAccountTemplate.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public AccountTemplate() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
protected void initialize() {
frmAccountTemplate = new JFrame();
frmAccountTemplate.setTitle(getFrameTitleName());
frmAccountTemplate.setBounds(100, 100, 450, 300);
frmAccountTemplate.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frmAccountTemplate.getContentPane().setLayout(null);
JButton btnAssignment = new JButton(getHomeworkBtnName());
btnAssignment.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frmAccountTemplate.dispose();
HW.main(null);
}
});
btnAssignment.setFont(new Font("Tahoma", Font.BOLD, 16));
btnAssignment.setBounds(15, 51, 200, 29);
frmAccountTemplate.getContentPane().add(btnAssignment);
}
}
HW:
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.filechooser.FileSystemView;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.awt.event.ActionEvent;
public class HW {
public JFrame frmHw;
/**
* Launch the application.
*/
public boolean getWriteBtnVisibility() {
return true;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
HW window = new HW();
window.frmHw.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public HW() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
protected void initialize() {
frmHw = new JFrame();
frmHw.setTitle("HW");
frmHw.setBounds(100, 100, 450, 300);
frmHw.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frmHw.getContentPane().setLayout(null);
JTextArea jTextArea1 = new JTextArea();
jTextArea1.setBounds(9, 11, 328, 197);
frmHw.getContentPane().add(jTextArea1);
JScrollPane scrollBar = new JScrollPane(jTextArea1);
scrollBar.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollBar.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frmHw.getContentPane().add(scrollBar);
scrollBar.setBounds(13, 39, 413, 189);
JButton btnWriteText = new JButton("Write Text");
btnWriteText.setVisible(getWriteBtnVisibility());
btnWriteText.setBounds(154, 11, 115, 24);
frmHw.getContentPane().add(btnWriteText);
}
}
When you made AccountTemplate extend the HW class, every method that was redefined in AccountTemplate overrided the original method from HW. The getWriteBtnVisibility is checked from within the HW's initialize method, but AccountTemplate's initialize method overrides it. Now StudentAccount inherits the overridden method which does not check for the getWriteBtnVisibility boolean and so the visibility is not modified.
If I understand correctly what you're trying to do is to extend the layout using inheritance, however to do that you need to create seperate methods that create and initialize the interface components and call them as required from class. Now you've only created a seperate method for the button name and visibility, but the method that checks for the visibility boolean has been overridden and no longer gets called.
You will have to redesign your architecture so that:
There is only one JFrame field (unless you want multiple interfaces)
There is only one method that creates the JFrame and sets up the title
All generic interface components are created using a seperate (final) method and can be called when required by the class that inherits it
A simple example
class HW {
public JFrame frame;
public String getFrameName() {
return "HW";
}
public boolean getHWBtnVisibility {return true;}
void setupHWComponents() {
JTextField field = new JTextField();
// ...
this.frame.getContentPane().add(field);
JButton button = new JButton("HW");
button.setVisible(getHWBtnVisibility());
// ...
}
void initFrame() {
this.frame = new JFrame(getFrameName());
// ....
}
void initialize() {
initFrame();
setupHWComponents();
}
}
And
class AccountTemplate {
public void setupTemplateComponents() {
JTextField loginField = new JTextField("login");
super.frame.getContentPane().add(loginField);
// ...
}
#Override
public void initialize() {
// Setup Frame and HW components
// If you dont want HW components, replace with initFrame()
super.initialize();
setupTemplateComponents();
}
}
Then the StudentAccount class can likewise choose what components to use and initialize and which ones it doesn't want and then it can add its own components to the frame.
The initialize method that matters, that sets the button's visibility is the one that is called from the HW class, and that method is only called from within AccountTemplate's button's ActionListener here:
public void actionPerformed(ActionEvent e) {
frmAccountTemplate.dispose();
HW.main(null);
}
Notice that it is a call to the HW main method, and so it is a HW instance that is being created when this happens (since that is what HW main creates), not a StudentAccount instance. This is why your inheritance doesn't work -- there is no inheritance happening when the button is created.
Having said that, I have to state that this code is overly complex, much more than it needs to be, and misuses inheritance in a way that only serves to confuse. Don't use inheritance (or absolute positioning and null layouts) since you're just complicating things needlessly. Simply your code and you will thank me.
If this were my project,
I would create distinct JPanels (not JFrames) for each type of GUI
I would use inheritance only for the program's model, the logical non-GUI aspects of the code, and not for the views (the GUI classes), and would use it very cautiously.
I would swap views using a CardLayout and not swap JFrames

Swing Progress Bar updates via Worker to EventDispatch thread

I have a JAVA6 GUI handling data import to our database. I have implemented a working JProgressBar. I understand that changes made to the GUI must be done via the event dispatch thread--which I do not think I am doing (properly/at all).
the background Worker thread, UploadWorker, is constructed by passing in the a JProgressBar created in the main program, and sets changes the value of the progress bar directly once it is finished:
// when constructed, this gets set to the main program's JProgressBar.
JProgressBar progress;
protected Void doInBackground() throws Exception {
write("<!-- Import starting at " + getCurrentTime() + " -->\n");
boolean chunked = false;
switch (importMethod) {
//do some importing
}
write("<!-- Import attempt completed at " + getCurrentTime() + "-->\n");
//here changes to the GUI are made
progress.setMaximum(0);
progress.setIndeterminate(false);
progress.setString("Finished Working");
return null;
}
This works fine, but sometimes(not always) throws me several NPE's in the std out, and users are complaining:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javax.swing.plaf.basic.BasicProgressBarUI.updateSizes(Unknown Source)
...etc...
Anyway, I believe there is something I need to do to get these updates executed on the proper thread, correct? How?
There are a number of ways you could do this, you could use the process method of the SwingWorker to also update the progress bar, but for me, this couples your worker to the UI, which isn't always desirable.
A better solution is to take advantage of the SwingWorkers progress and PropertyChange support, for example....
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
switch (worker.getState()) {
case DONE:
// Clean up here...
break;
}
} else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
// You could get the SwingWorker and use getProgress, but I'm lazy...
pb.setIndeterminate(false);
pb.setValue((Integer)evt.getNewValue());
}
}
});
worker.execute();
This means you could do this for ANY SwingWorker, so long as it was the worker was calling setProgress internally...
public static class ProgressWorker extends SwingWorker {
public static final int MAX = 1000;
#Override
protected Object doInBackground() throws Exception {
for (int index = 0; index < MAX; index++) {
Thread.sleep(250);
setProgress(Math.round((index / (float)MAX) * 100f));
}
return null;
}
}
The benefit of this is that the PropertyChange event notification is called from within the context of the of Event Dispatching Thread, making it safe to update the UI from within.
And fully runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SwingWorkerProgressExample {
public static void main(String[] args) {
new SwingWorkerProgressExample();
}
public SwingWorkerProgressExample() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pb;
public TestPane() {
setLayout(new GridBagLayout());
pb = new JProgressBar(0, 100);
pb.setIndeterminate(true);
add(pb);
ProgressWorker worker = new ProgressWorker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
switch (worker.getState()) {
case DONE:
// Clean up here...
break;
}
} else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
// You could get the SwingWorker and use getProgress, but I'm lazy...
System.out.println(EventQueue.isDispatchThread());
pb.setIndeterminate(false);
pb.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class ProgressWorker extends SwingWorker {
public static final int MAX = 1000;
#Override
protected Object doInBackground() throws Exception {
for (int index = 0; index < MAX; index++) {
Thread.sleep(250);
setProgress(Math.round((index / (float) MAX) * 100f));
}
return null;
}
}
}
You can just create a new Runnable that performs GUI updates and invoke it in a GUI thread using SwingUtilities.invokeLater

What kind of voodoo is necessary to get the glassPane visible and valid for an JInternalFrame?

I have a project where I use JDesktopPane for the main application and a bunch of JInternalFrames for a series of independent analyses.
Certain bits of the analyses are time-consuming thus I run them on SwingWorkers, but I would like to both disable the GUI (so no actions are queued) and inform the user that some action is going on and that it's normal.
Previously I have used a custom GlassPane for that purpose, and it has worked nicely before. Now I am experiencing some issues, using the same class as before. Specifically, the glassPane intercepts user input, expected but no visual cue is visible, which makes me think that the paintComponent() is never called on the glassPane.
Just to be sure I googled and came across another implementation (called DisabledGlassPane) of the "please-wait-glassPane" concept but to no success really. While trying to debug the issue I realised that when I start/activate my glassPane it is invalid by default and does not get validated by itself.
If I specifically call validate() on the JInternalFrame after activating the glassPane, it appears to be valid and visible, based on the properties of the glassPane but I see nothing on the screen (both GlassPane implementations have color and text based features that should be immediately visible to the user).
EDIT:
Below is the relevant piece of the code, extracted out of the bigger scheme of things into a minimalist, self-contained (with the exception of the DisabledGlassPane class mentioned above, omitted for the sake of brevity) example. When I run the DesktopFrame class below, and click the button the calculations start, the cursor changes to waiting mode, however the screen is not grayed out, and the message to the user is not displayed, hence my suspicion of paintComponent is never actually called..
I am primarily wondering if I have made an obvious miss, since I am not that experienced with GUI programming and Swing.
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.SwingWorker;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
public class DesktopFrame extends JFrame implements InternalFrameListener{
private JDesktopPane dpane;
private JInternalFrame f;
static DisabledGlassPane gp = new DisabledGlassPane();
public DesktopFrame() {
dpane = new javax.swing.JDesktopPane();
dpane.setPreferredSize(new java.awt.Dimension(1020, 778));
setContentPane(dpane);
addFrame();
pack();
}
public JInternalFrame addFrame(){
f = new JInternalFrame("test");
f.setGlassPane(gp);
f.addInternalFrameListener(this);
f.setLayout(new GridLayout());
f.setPreferredSize(new java.awt.Dimension(400,300));
f.add(new javax.swing.JLabel("something something"));
f.add(new javax.swing.JTextArea(10, 10));
javax.swing.JButton but = new JButton("click me!");
but.setPreferredSize(new java.awt.Dimension(100,50));
but.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
gp.activate("Please wait...");
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
for(float i=-3000; i < 3000; i = i + 0.01f){
double exp = Math.pow(2,i);
double fac = Math.pow(i, 2);
System.out.println(exp/fac);
}
return null;
}
};
worker.execute();
try {
if(worker.get() == null)
gp.deactivate();
} catch (InterruptedException | ExecutionException e1) {
e1.printStackTrace();
}
}
});
f.add(but);
f.setVisible(true);
f.pack();
dpane.add(f);
try {
f.setSelected(true);
} catch (java.beans.PropertyVetoException e) {
e.printStackTrace();
}
dpane.repaint();
return f;
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
DesktopFrame df = new DesktopFrame();
df.setLocationRelativeTo(null);
df.setVisible(true);
df.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
});
}
#Override
public void internalFrameOpened(InternalFrameEvent e) {}
#Override
public void internalFrameClosing(InternalFrameEvent e) {}
#Override
public void internalFrameClosed(InternalFrameEvent e) {}
#Override
public void internalFrameIconified(InternalFrameEvent e) {}
#Override
public void internalFrameDeiconified(InternalFrameEvent e) {}
#Override
public void internalFrameActivated(InternalFrameEvent e) {}
#Override
public void internalFrameDeactivated(InternalFrameEvent e) {}
}
but I would like to both disable the GUI (so no actions are queued) and inform the user that some action is going on and that it's normal.
Check out the Disable Glass Pane for a general solution you might be able to use. The above class intercepts mouse and key events and allows you to display a message while the glass pane is visible.

Stop a loop in java with graphical interface (buttons)

I created a graphic interface in java and 2 buttons.
My aim :
1) When I click on the first button, having a loop in which different tasks are processed (Button "Start"). Between each loop there is a stop of 10 seconds
2) When I click on the second button, the loop is processed immediately one last time but then stopped.
(I also would like to make a pop up showing that it has been stopped but that's not the main question, I think I can do it.)
I tried the following code, but first I think they are more simple ways to sort my problem. Plus I can compile but it doesn't work, the loop is not stopped, the window crashes:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
globalStop="Run";
while (globalStop.equals("Run")) {
System.out.println("GO");
// Other stuff
// For the break ?
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Logger.getLogger(main.class.getName()).log(Level.SEVERE, null, ex);
}
}
System.out.println("done");
}
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
globalStop = "Stop";
System.out.println("Bouton2");
}
I hope I was clear enough, if that is not the case, please let me know and I will rephrase.
Thank you all in advance for your help.
I wondered how long it would take me to create a United States type traffic signal GUI. It took 75 minutes. I was able to create the GUI quickly because a lot of Swing is boilerplate. Once you create one GUI, you can copy some of the classes for your next GUI.
Here's an image of the traffic signal GUI.
When you press the Start button, the traffic signal will cycle from green to yellow to red. The traffic signal will cycle forever, until you press the Stop button.
When you press the Stop button, the traffic signal will turn red. It will stay red forever, until you press the Start button.
When you press the Start button while the traffic signal is cycling, the green to yellow to red cycle starts over.
Basically, the following steps show you how to create any Swing GUI. I didn't create the code in this order, but it makes sense to explain the code in a logical order. So, let's dig into the code.
This is the model class for the GUI. Every GUI needs to have it's own model, separate from the model of the application. For this GUI, the model is simple.
package com.ggl.traffic.signal.model;
import java.awt.Dimension;
public class TrafficSignalModel {
public static final int RED_LIGHT_TIME = 15;
public static final int YELLOW_LIGHT_TIME = 5;
public static final int GREEN_LIGHT_TIME = 10;
public static final Dimension LIGHT_SIZE = new Dimension(32, 32);
}
We set the signal light times in the model, as well as the size of the traffic lights.
For a more complicated GUI, we would keep track of the field values in the model.
Next, we have the main class of the traffic signal GUI.
package com.ggl.traffic.signal;
import javax.swing.SwingUtilities;
import com.ggl.traffic.signal.view.TrafficSignalFrame;
public class TrafficSignal implements Runnable {
#Override
public void run() {
new TrafficSignalFrame();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new TrafficSignal());
}
}
This class ensures that the traffic signal GUI is on the Swing event thread. That's all this class does. You can see how you can copy this class to start any GUI.
Next, we have the Frame class of the GUI.
package com.ggl.traffic.signal.view;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
public class TrafficSignalFrame {
protected ButtonPanel bPanel;
protected JFrame frame;
protected TrafficSignalPanel tsPanel;
public TrafficSignalFrame() {
createPartControl();
}
protected void createPartControl() {
tsPanel = new TrafficSignalPanel();
bPanel = new ButtonPanel();
bPanel.setTrafficSignalPanel(tsPanel);
frame = new JFrame();
frame.setTitle("Traffic Signal");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
frame.setLayout(new FlowLayout());
frame.add(bPanel.getPanel());
frame.add(tsPanel.getPanel());
frame.pack();
// frame.setBounds(100, 100, 400, 200);
frame.setVisible(true);
}
public void exitProcedure() {
frame.dispose();
System.exit(0);
}
public JFrame getFrame() {
return frame;
}
}
This class is boilerplate, except for the particular JPanels that will make up the GUI. If your JFrame has a JMenu, this would be the place to attach your JMenu to your JFrame.
Notice that I did not extend JFrame to make this class. The only time you extend a Swing component is when you're overriding one or more of the component's methods. If I need the actual JFrame, I call the getFrame() method. Using Swing components rather than extending Swing components keeps my methods separate from the Swing methods.
Next, we'll look at the traffic signal light panel. This panel makes up one of the 3 lights in the traffic signal.
package com.ggl.traffic.signal.view;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class TrafficSignalLightPanel extends JPanel {
private static final long serialVersionUID = 1L;
protected boolean lightOn;
protected Color lightColor;
protected Color darkColor;
public TrafficSignalLightPanel(Color lightColor) {
this.lightColor = lightColor;
this.darkColor = Color.WHITE;
this.lightOn = false;
}
public void setLightOn(boolean lightOn) {
this.lightOn = lightOn;
this.repaint();
}
#Override
public void paintComponent(Graphics g) {
if (lightOn) {
g.setColor(lightColor);
} else {
g.setColor(darkColor);
}
g.fillRect(0, 0, getWidth(), getHeight());
}
}
This class extends JPanel, because we want to override the paintComponent method. This is a simple class. All it does is paint the panel a color, or white.
Next, we'll look at the traffic signal panel. This panel creates 3 light panels and arranges them in a vertical row.
package com.ggl.traffic.signal.view;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.border.Border;
import com.ggl.traffic.signal.model.TrafficSignalModel;
public class TrafficSignalPanel {
protected JPanel panel;
protected TrafficSignalLightPanel redLight;
protected TrafficSignalLightPanel yellowLight;
protected TrafficSignalLightPanel greenLight;
public TrafficSignalPanel() {
createPartControl();
}
protected void createPartControl() {
Border border = BorderFactory.createLineBorder(Color.BLACK, 4);
redLight = new TrafficSignalLightPanel(Color.RED);
redLight.setBorder(border);
redLight.setPreferredSize(TrafficSignalModel.LIGHT_SIZE);
yellowLight = new TrafficSignalLightPanel(Color.YELLOW);
yellowLight.setBorder(border);
yellowLight.setPreferredSize(TrafficSignalModel.LIGHT_SIZE);
greenLight = new TrafficSignalLightPanel(Color.GREEN);
greenLight.setBorder(border);
greenLight.setPreferredSize(TrafficSignalModel.LIGHT_SIZE);
panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.setPreferredSize(
new Dimension(TrafficSignalModel.LIGHT_SIZE.width + 10,
TrafficSignalModel.LIGHT_SIZE.height * 3 + 25));
panel.add(redLight);
panel.add(yellowLight);
panel.add(greenLight);
}
public JPanel getPanel() {
return panel;
}
public TrafficSignalLightPanel getRedLight() {
return redLight;
}
public TrafficSignalLightPanel getYellowLight() {
return yellowLight;
}
public TrafficSignalLightPanel getGreenLight() {
return greenLight;
}
}
A fairly straightforward creation of a JPanel from 3 JPanels. I set the preferred size of the JPanel so the lights will be in a vertical row.
Next, we'll look at the button panel. You can pretty much copy this code into any GUI that has a button panel.
package com.ggl.traffic.signal.view;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
import com.ggl.traffic.signal.thread.TrafficSignalCycle;
public class ButtonPanel {
protected JButton startButton;
protected JButton stopButton;
protected JPanel panel;
protected TrafficSignalCycle thread;
protected TrafficSignalPanel tsPanel;
public ButtonPanel() {
this.thread = null;
createPartControl();
}
protected void createPartControl() {
panel = new JPanel();
panel.setLayout(new FlowLayout());
startButton = new JButton("Start");
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
if (thread != null) {
thread.stopRunning();
}
tsPanel.getRedLight().setLightOn(false);
tsPanel.getYellowLight().setLightOn(false);
tsPanel.getGreenLight().setLightOn(false);
thread = new TrafficSignalCycle(tsPanel);
thread.start();
}
});
panel.add(startButton);
stopButton = new JButton("Stop");
stopButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
if (thread != null) {
thread.stopRunning();
thread = null;
}
tsPanel.getRedLight().setLightOn(true);
tsPanel.getYellowLight().setLightOn(false);
tsPanel.getGreenLight().setLightOn(false);
}
});
panel.add(stopButton);
setButtonSizes(startButton, stopButton);
}
protected void setButtonSizes(JButton ... buttons) {
Dimension preferredSize = new Dimension();
for (JButton button : buttons) {
Dimension d = button.getPreferredSize();
preferredSize = setLarger(preferredSize, d);
}
for (JButton button : buttons) {
button.setPreferredSize(preferredSize);
}
}
protected Dimension setLarger(Dimension a, Dimension b) {
Dimension d = new Dimension();
d.height = Math.max(a.height, b.height);
d.width = Math.max(a.width, b.width);
return d;
}
public void setTrafficSignalPanel(TrafficSignalPanel tsPanel) {
this.tsPanel = tsPanel;
}
public JPanel getPanel() {
return panel;
}
}
The button actions were simple enough that I could keep them in the button panel. If you want, you can code separate action classes.
Finally, here's the code that runs the traffic light cycle. It's an extension of the Thread class, so it can be run in a separate thread from the GUI. It's always a good idea to do work in threads separate from the GUI thread.
package com.ggl.traffic.signal.thread;
import javax.swing.SwingUtilities;
import com.ggl.traffic.signal.model.TrafficSignalModel;
import com.ggl.traffic.signal.view.TrafficSignalLightPanel;
import com.ggl.traffic.signal.view.TrafficSignalPanel;
public class TrafficSignalCycle extends Thread {
protected boolean isRunning;
protected boolean isFinished;
protected TrafficSignalPanel tsPanel;
public TrafficSignalCycle(TrafficSignalPanel tsPanel) {
this.tsPanel = tsPanel;
this.isRunning = true;
this.isFinished = false;
}
#Override
public void run() {
while (isRunning) {
signalLightOn(tsPanel.getGreenLight(), TrafficSignalModel.GREEN_LIGHT_TIME);
signalLightOn(tsPanel.getYellowLight(), TrafficSignalModel.YELLOW_LIGHT_TIME);
signalLightOn(tsPanel.getRedLight(), TrafficSignalModel.RED_LIGHT_TIME);
}
this.isFinished = true;
}
protected void signalLightOn(TrafficSignalLightPanel light, int seconds) {
if (isRunning) {
setLightOn(light, true);
}
for (int i = 0; i < 1000 && isRunning; i++) {
try {
Thread.sleep(1L * seconds);
} catch (InterruptedException e) {
}
}
setLightOn(light, false);
}
protected void setLightOn(final TrafficSignalLightPanel light,
final boolean isLightOn) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
light.setLightOn(isLightOn);
}
});
}
public void stopRunning() {
this.isRunning = false;
while (!isFinished) {
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
}
}
}
}
The method that actually changes the color of the signal light must execute in the Swing event thread. That's what the setLightOn method does by calling SwingUtilities.
The timing loop is a bit complicated because we want to be able to stop the thread in a few milliseconds. The isFinished boolean ensures that the thread is stopped completely, so that the lights can be set.
This is a fairly long answer, but I hope it's helpful to anyone creating a Swing GUI.
You shouldn't be looping within the UI thread, nor telling it to sleep. Fundamentally you should keep the UI thread as free as possible.
If you need something to occur on a regular basis in a Swing UI in the UI thread, use a Swing Timer.
It's unclear what you're doing in the "other stuff" however - it's possible that you should be doing that in a different thread entirely and using (say) an AtomicBoolean to indicate when you want to stop.
1. You should always keep the UI thread for UI work and Non-UI thread for Non-UI work.
2. In Java GUI, the main() is not Long lived, after assigning the construction of GUI to the Event Dispatcher Thread, the main() quits, and now its EDT's responsibility handle the GUI.
3. So when you click the buttons, and the work you are doing is doing some heavy process or its time consuming....then span a Separate thread.
4. You can use Thread or SwingWorker.
Example:
Button b = new Button("Click me");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
Thread t = new Thread(new Runnable(){
public void run(){
// Do the Heavy Processing work.....
}
});
t.start();
}
});
The easy but dirty way:
Multi-thread your program and have one thread do your loop and a second thread monitor your buttons. Have the button change your globalStop variable
The not so easy but cleaner way:
Make the button throw an interrupt to change the value. After the interrupt the for loop will continue to the end.

Categories