Consider the following program. Copy the code into files, compile and run this program. Clicking on the button toggles between the text LOVE and HATE. If you run it with argument 0, you can type one of the colors green, red, blue or cyan into the terminal window and it changes the color of the text. If you run it with argument 1, you need to click on the "mystery" button in order to be able to enter a color into the terminal.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class L10a {
public static void main(String[] args) {
if( args.length == 0 ) {
System.out.println("Run with argument 0 or 1!");
System.exit(0);
}
boolean flag = args[0].equals("1");
JLabel label = new JLabel(" LOVE ");
label.setForeground(Color.RED);
JButton b1 = new JButton("click to toggle");
b1.addActionListener(new Toggler(label));
JButton b2 = new JButton("mystery");
if( flag )
b2.addActionListener(new Mystery(label));
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(label, BorderLayout.WEST);
f.add(b1, BorderLayout.CENTER);
f.add(b2, BorderLayout.EAST);
f.pack();
f.setVisible(true);
if( !flag ) {
while (true) {
CChange.changeColor(label);
}
}
}
}
import javax.swing.*;
import java.awt.*;
public class CChange {
public static void changeColor(JLabel label) {
System.out.print("new color: ");
String s = System.console().readLine();
Color c = label.getForeground();
if( s.equals("red") )
c = Color.RED;
else if( s.equals("blue") )
c = Color.BLUE;
else if( s.equals("green") )
c = Color.GREEN;
else if( s.equals("cyan") )
c = Color.CYAN;
else
System.out.println("Unknown color!");
label.setForeground(c);
}
}
import javax.swing.*;
import java.awt.event.*;
public class Toggler implements ActionListener {
private JLabel label;
public Toggler(JLabel label) {
this.label = label;
}
public void actionPerformed(ActionEvent e) {
label.setText(label.getText().equals(" LOVE ") ? " HATE " : " LOVE ");
}
}
import javax.swing.*;
import java.awt.event.*;
public class Mystery implements ActionListener {
private JLabel label;
public Mystery(JLabel label) {
this.label = label;
}
public void actionPerformed(ActionEvent e) {
CChange.changeColor(label);
}
}
changeColor
method
System.out.println("Thread ID: " + Thread.currentThread().getId());
recompile, and then try the same two runs you did in the
previous step. What's the difference?
What's going on here? Well it turns out that all GUI programs are inherently multi-threaded. There's the main thread (ID 1), which any program has. There's also a thread called the "event dispatch thread", and that's the thread on which all calls to GUI "listeners" (ActionListeners, WindowListeners, etc) occur. Somewhere on the event dispatch thread's call-stack is a record for a call to a function that is essentially an infinite loop that waits for the next GUI action, and then starts calling the necessary functions to respond - a process that eventually results in listeners being called. After all those calls are made, the stack eventually goes back down to the function with the infinite loop ... which waits around for the next GUI action.
So now think about our example above: with argument 0, the call to CChange.changeColor() was made in main(), which means a new record for the CChange.changeColor() call was made on the Thread 1 call stack. Thus, the event dispatch thread was left free to wait for events and, ultimately, to call the Toggler actionPerformed() method. On the other hand, with argument 1, the call to CChange.changeColor() was made in Mystery's actionPerformed() method, which means a new record on the event dispatch thread's call stack. Thus, the event dispatch thread couldn't do anything until the call to CChange.changeColor() returned, which required the user to complete typing the color name and press enter. In other words, the GUI was locked up until we finished!
This is a huge issue in programming GUIs with Java's Swing API: you can't execute any method on the event dispatch thread that doesn't return really quickly. Otherwise you'll lock up the GUI. So what do you do if a GUI event like a mouse click is supposed to initiate some long-running function call? Well, you create a new Thread for it to run in!
Your job is to fix the Mystery Button so that the GUI doesn't lock up while the user dithers over which color to enter. Concretely, this means that when you press the Mystery Button, the toggle button and the window's "x" for closing still work, even if the user hasn't yet typed a color and pressed enter. That means, spawning a new thread for the CChange.changeColor() to execute in.
You'll want to keep running with mode: java L10a 1
Note: If you think that you've got it solved, you might want to
look into the issue of what happens if you go crazy and
double-click or triple-click the Mystery Button. It probably doesn't
quite work the way you'd like. Look at Thread's isAlive()
method.
With a few tweaks, you can make it so that after a click
of the Mystery Button, all subsequent clicks are ignored until
a color is entered in response to the first one.
Fixing this issue now directly translates to preventing similar errors in Part 2 below.
Think that you've got it? Demo this part to your instructor!
If not during this lab, then demo it anytime during the week, or
(worst case) at the beginning of the next lab period.
Now let's take what we've learned and produce a simple, and yet
useful tool: a GUI timer. The program should be called L10Timer,
i.e. you should run it like this:
java L10TimerThe timer looks like this when it is launched
Here are a few useful tidbits for you:
Thread.sleep(1000);
will put the
thread in which it is executed to sleep for one second.
Look at
the sleep() API documentation carefully ... there might be exceptions!
JLabel lab = new JLabel("foo");
lab.setPreferredSize(new Dimension(60,15));
JTextField tf = new JTextField(10);
Note too that you can call setText
on a
JTextField object to set the text that appears, even though
the user might go change it later.
Demo your fully tested solution to your instructor!
If not during this lab, then demo it at the beginning of the next lab period.
Submit all the java files you need to compile and run your
L10Timer program with the following terminal command:
~/bin/submit -c=IC211 -p=Lab10 *.javaDo NOT submit any unused .java files, any .class files, or files that do not compile!