I'm trying to make a clock using as few resources as possible and just relying on my (limited) knowledge of Java. I've come to a road block however. The clock I wrote works, except rather than the text in the jlabel being replaced, it overlaps itself. I've tried fixing this by clearing the value of timeStamp, but it doesn't seem to be working.
public class Clock extends JFrame{
public static void main (String args[]) {
Clock gui = new Clock();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setSize(50,50);
gui.setVisible(true);
gui.setTitle("Clock");
int a = 1;
while (a == 1){
String timeStamp = new SimpleDateFormat("hh:mm:ss a").format(Calendar.getInstance().getTime());
JLabel label;
label = new JLabel();
label.setText(String.valueOf(timeStamp));
timeStamp = "";
gui.add(label);
label.revalidate();
}
}
}
You should not be creating a new JLabel every iteration.
JLabel label = new JLabel();
gui.add(label);
while (a == 1){
String timeStamp = new SimpleDateFormat("hh:mm:ss a").format(Calendar.getInstance().getTime());
label.setText(String.valueOf(timeStamp));
timeStamp = "";
label.revalidate();
}
You should use a SwingWorker to update the clock. Currently you're doing it on the event dispatch thread and thus interfere with the UI rendering.
Besides that you should reuse the label instead of creating a new one for each timestamp. Currently you're stacking labels on top of each other since gui.add() will just add the new label and won't remove the old ones.
Why are you creating a new JLabel in every iteration of the loop?
Don't do that.
Just create a single label in Clock's constructor.
Also, changing the label's text should be done on the event thread, not the main thread.
While the code you wrote "works", you're missing some things to make a stable Swing GUI.
You must always start a Swing application using the SwingUtilities invokelater method. This puts the creation and the execution of the Swing components on the Event Dispatch thread (EDT).
I separated the creation of the GUI from the execution of the GUI. Separation of concerns makes coding each part easier.
In the Timer Runnable, I again use the SwingUtilities invokeLater method to make sure that the updating of the JTextField with the time happens on the EDT.
I stop the Thread before I exit. Generally, you should stop any threads you start, and not rely on the JVM to clean up for you.
Here's the clock.
And here's the code.
package com.ggl.testing;
import java.awt.Color;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class SimpleClock implements Runnable {
private JFrame frame;
private JPanel panel;
private JTextField clockDisplay;
private Timer timer;
#Override
public void run() {
frame = new JFrame("Clock");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
panel = new JPanel();
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 6));
clockDisplay = new JTextField(12);
clockDisplay.setEditable(false);
clockDisplay.setHorizontalAlignment(JTextField.CENTER);
panel.add(clockDisplay);
frame.add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
timer = new Timer(this);
new Thread(timer).start();
}
public void exitProcedure() {
timer.setRunning(false);
frame.dispose();
System.exit(0);
}
public void setText(String text) {
clockDisplay.setText(text);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Clock());
}
public class Timer implements Runnable {
private volatile boolean running;
private SimpleClock clock;
private SimpleDateFormat timeFormat;
public Timer(SimpleClock clock) {
this.clock = clock;
this.running = true;
this.timeFormat = new SimpleDateFormat("h:mm:ss a");
}
#Override
public void run() {
while (running) {
displayTime();
sleep();
}
}
public void displayTime() {
final Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
final String s = timeFormat.format(date);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
clock.setText(s);
}
});
}
public void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
}
Related
I'm currently stuck on a project at the moment component("c") cannot be resolved, I know I'm getting this issue as I'm trying to grab "c" from outside the class from the repaint method but have no idea how to get around this. As far as I'm concerned if I am able to grab "c" then my whole program would. Any help will be massively appreciated. thanks!
package adp.cwr2122;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
public class SomApplication extends JFrame {
private static final long serialVersionUID = 1L;
private final JProgressBar bar = new JProgressBar();
private final SoM som = new SoM(800,600);
public SomApplication() {
final JPanel mainPanel = new JPanel(new BorderLayout());
final JPanel southPanel = new JPanel(new BorderLayout());
final JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(ev -> doCancel());
this.bar.setMaximum(5000);
southPanel.add(this.bar);
southPanel.add(cancelButton, BorderLayout.EAST);
final SomComponent c = new SomComponent(this.som);
mainPanel.add(c);
mainPanel.add(southPanel, BorderLayout.SOUTH);
add(mainPanel);
pack();
setVisible(true);
this.som.initialise();
c.repaint();
final long start = System.currentTimeMillis();
while(SomApplication.this.som.range() > 0) {
SomApplication.this.som.doOne();
SomApplication.this.bar.setValue((int)((5000 * (SomApplication.this.som.maxRange() - SomApplication.this.som.range())) / SomApplication.this.som.maxRange()));
c.repaint();
}
final long elapsed = System.currentTimeMillis() - start;
System.out.println("Done " + elapsed);
}
private void doCancel() {
System.out.println( "Cancel button pressed");
}
private static class SomComponent extends JComponent {
private static final long serialVersionUID = 1L;
private final SoM som;
public SomComponent( final SoM som) {
this.som = som;
new Thread(new Runnable() {
public void run(){
queuePaint();
System.out.println("Thread is working");
}
}).start();
}
public void repaint() {
if(SwingUtilities.isEventDispatchThread()) {
c.repaint();
}
else {
SwingUtilities.invokeLater(c.repaint);
}
}
public void queuePaint() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
System.out.println("Queue Paint is being called");
setPaint(); }
});
}
public void setPaint() {
System.out.println("Paint is being called");
paintComponent(this.getGraphics());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(this.som.image().getWidth(), this.som.image().getHeight());
}
#Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
g.drawImage(this.som.image(), 0, 0, this);
repaint();
}
}
public static void launch() {
new SomApplication();
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new SomApplication();
}
});
}
}
As you have recognized yourself your problem is access to c in the repaint() method. You only declare c in the scope of the constructor of SomApplication, so everything outside that scope (read: outside the curly braces of the SomApplicaton constructor) has no idea what c is supposed to be, because it is not declared.
One way to resolve this is declaring c as a member of SomApplication. However you will run into an additional problem there. Your nested class is static, so if you declare c as a member of SomApplication, then SomComponent cannot be static in order to "see" c.
Having said all that there is something fishy with your code there. Because c is an instance of SomComponent and presumably you intend to update itself I then don't see why c is in repaint() to begin with. Your repaint() does not override the JComponent#repaint and your c is presumably supposed to be this.
It's possible I misunderstand something about your architecture, but are you sure you need the c at all in there? Why don't you call repaint() rather than c.repaint() in SomComponent?
If you took the code piece in your SomComponent#repaint method from some online example, take a look at whether you need that code at all. JComponent already comes with repaint(), so maybe this is just the result of a bad copy+paste. Not sure.
I am slightly confused, I have a jFrame of which I have made in Netbeans. This jFrame has a jLabel, of which is set to setVisible(false); from the beginning. Whenever a specific method is called, I then set the jLabel to setVisible(true); and then use a timer to set it to false again after 2 seconds. Apparently it won't work and I am unable to figure out why. I am aware of the repaint(); method, but can figure out how to make that work either.
I know the actual method for setting the visibility is called, as I have set it to print a line with the current state, which it does.
My actual code is the one below.
public JFram() {
initComponents();
setResizable(false);
jLabel2.setVisible(false);
}
static void tesMethod() {
try {
//function that does something
} finally {
new JFram().showHide(); //call function which is supposed to change the vissibility of jLabel
}
}
void showHide() {
jLabel2.setVisible(true);
System.out.println("reached show");
new java.util.Timer().schedule(
new java.util.TimerTask() {
#Override
public void run() {
jLabel2.setVisible(false);
System.out.println("reached timer");
}
},
2000
);
}
The code below here is how I tried to use the repaint(); method.
void showHide() {
jLabel2.setVisible(true);
jLabel2.repaint();
System.out.println("reached show");
new java.util.Timer().schedule(
new java.util.TimerTask() {
#Override
public void run() {
jLabel2.setVisible(false);
jLabel2.repaint();
System.out.println("reached timer");
}
},
2000
);
}
I think your problem lies mainly in you using a java.util.Timer instead of a javax.swing.Timer and probably you're blocking the Event Dispatch Thread (EDT).
You could try this code and compare it with yours, I also don't see where you're adding your JLabel to your frame.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ShyLabel {
private JFrame frame;
private JLabel label;
private Timer timer;
private boolean isVisible;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ShyLabel().createAndShowGui();
}
});
}
public void createAndShowGui() {
String labelText = "I'm a shy label that hides every 2 seconds";
isVisible = true;
frame = new JFrame(getClass().getSimpleName());
label = new JLabel(labelText);
timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
label.setText(isVisible ? "" : labelText);
isVisible = !isVisible;
}
});
timer.setInitialDelay(2000);
timer.start();
frame.add(label);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
The below image is produced by the above code, however because of the time I recorded the GIF it looks really fast instead of taking 2 seconds as it should be...
May be it is a problem of layout.
As you set resizable to false before any layout calculation occurred, the label was ignored (as invisible) by the time of the first layout.
You could try revalidate().
I would like to disable a JButton for about 10 seconds. Is there way to do this?
Thank you
Use a Swing Timer, when triggered, it notifies the registered listener within the context of the Event Dispatching Thread, making it safe to update the UI from.
See How to use Swing Timers and Concurrency in Swing for more details
First read the answer from #MadProgrammer and go through the links provided there. If you still need a working example based on those suggestions, following is one.
why the solution is better than few solutions presented
It's because it uses a javax.swing.Timer to enable the button that enables GUI related tasks to be automatically executed on the event-dispatch thread (EDT). This saves the swing application from being intermixed with non EDT operations.
Please try the following example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SwingDemo extends JPanel {
private final JButton button;
private final Timer stopwatch;
private final int SEC = 10;
public SwingDemo() {
button = new JButton("Click me to disable for " + SEC + " secs");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JButton toDisable = (JButton) e.getSource();
toDisable.setEnabled(false);
stopwatch.start();
}
});
add(button);
stopwatch = new Timer(SEC * 1000, new MyTimerListener(button));
stopwatch.setRepeats(false);
}
static class MyTimerListener implements ActionListener {
JComponent target;
public MyTimerListener(JComponent target) {
this.target = target;
}
#Override
public void actionPerformed(ActionEvent e) {
target.setEnabled(true);
}
}
public static void main(String[] args) {
final JFrame myApp = new JFrame();
myApp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myApp.setContentPane(new SwingDemo());
myApp.pack();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
myApp.setVisible(true);
}
});
}
}
You can use Thread, Task or the simpler Timer class.
you can use Thread.sleep(time in mil seconds)
ex:
Thread.sleep(10000); // sleep for 10 seconds
JButton button = new JButton("Test");
try {
button.setEnabled(false);
Thread.sleep(10000);
button.setEnabled(true);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
but it must be in a separate thread or it will make all the GUI hang for 10 seconds.
you can post more details about the code and i can help
I have a function graphics() that creates my JFrame and two JRadioButtons and adds ActionListeners to them. This graphics is called from main() and graphics itself calls game().
public void game() throws Exception
{
jTextArea1.setLineWrap(true);
jTextArea1.setWrapStyleWord(true);
jTextArea1.setText("This is private information.");
jRadioButton1.setVisible(true);
jRadioButton2.setVisible(true);
try {
t.sleep(40000);
repaint();
} catch (InterruptedException e) {
// We've been interrupted: no more messages.
return;
}
After displaying "This is private information." in the text Area, I want the program execution to pause for 40 seconds, or until the user presses the JRadioButton, whichever is earlier. So I added an ActionListener and called t.interrupt() inside it.
private void jRadioButton1ActionPerformed(java.awt.event.ActionEvent evt) {
t.interrupt();
jRadioButton1.setVisible(false);
jRadioButton2.setVisible(false);
//System.out.println(t.interrupted());
jTextArea1.setText("Please wait...");
}
However, even after choosing the JRadioButton which should trigger the interrupt, that does not happen and t.interrupted returns false.
Any help would be appreciated.
Never, ever call Thread.sleep(...) on the Swing event thread as you will freeze the thread and effectively freeze your program. The solution is to consider use of a Swing Timer for the time-dependent portion of your requirement and using a SelectionListener for the JCheckBox or JRadioButton requirement.
For example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
public class PausingExecution extends JPanel {
private static final String SELECTED_TEXT = "Snafus are Better!!!";
private static final String UNSELECTED_TEXT = "Fubars Rule!!";
private static final String TIMES_UP = "Time's Up!!!!";
private static final int TIMER_DELAY = 10 * 1000;
private JTextField messageField = new JTextField(UNSELECTED_TEXT, 10);
private JCheckBox checkBox = new JCheckBox("Click Me");
public PausingExecution() {
add(messageField);
add(checkBox);
checkBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent iEvt) {
if (iEvt.getStateChange() == ItemEvent.SELECTED) {
messageField.setText(SELECTED_TEXT);
} else {
messageField.setText(UNSELECTED_TEXT);
}
}
});
Timer mySwingTimer = new Timer(TIMER_DELAY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
messageField.setText(TIMES_UP);
checkBox.setEnabled(false);
}
});
mySwingTimer.setRepeats(false);
mySwingTimer.start();
}
private static void createAndShowGui() {
PausingExecution mainPanel = new PausingExecution();
JFrame frame = new JFrame("PausingExecution");
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();
}
});
}
}
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.