| Animation by Example: Adding
Controls by David Matuszek |
Now it's time to add some controls to our applet. We will do the simple thing and add a single button, labeled Start. When clicked, the animation will begin, and the label on the button will change to Stop.
Here's the applet:
In the Controller class, we create a new Panel and a Button to put on it:
// Declare components here, where they are visible to inner classes
Panel buttonPanel = new Panel ();
Button runButton = new Button (" Start ");
In the Controller class's init() method, we add these components
to the applet:
buttonPanel.add (runButton); buttonPanel.setBackground(Color.lightGray); this.add (BorderLayout.SOUTH, buttonPanel);Also in the
init() method, we attach an action to the button. Fortunately,
the View already checks an okToRun variable (see, we were thinking
ahead!), so all we have to do is set that variable appropriately and change the
label on the button.
// Attach actions to components
runButton.addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent event) {
if (view.okToRun) {
view.okToRun = false;
showStatus ("Animation is stopped.");
runButton.setLabel (" Start ");
}
else {
view.okToRun = true;
showStatus ("Animation is running.");
runButton.setLabel (" Stop ");
}
}});
Multithreaded programs can have race conditions--if two Threads use the same variable, who gets it first?
It's worth looking at the View.run() method:
public void run() {
// "alive" will be true until the Applet is destroyed
while(alive) {
// "okToRun" will be true if the Applet is started and not stopped
if(okToRun) {
model.makeOneStep();
repaint(); // Note: uses Graphics for this Canvas, not for the Applet
}
// Control the speed of execution
try { Thread.sleep(delay); }
catch(InterruptedException e) {}
}
}
This method uses two variables, alive and okToRun,
to control execution.
When the run() method finishes, its Thread dies. When we quit
the applet, we want this to happen. We want the Thread to die, otherwise it
keeps running and slows down our computer. We can do this by setting alive
to false. But we have to be sure to set alive to
true before starting this Thread, otherwise the Thread might start,
check alive, and die, before the applet even gets started.
The okToRun variable doesn't kill the thread, it just keeps it
from updating the model or calling repaint() (which calls
update(Graphics g)).But run() still has to sleep for a while
(so it doesn't waste computer time that the other Threads could be using). However,
run() isn't the only way to get to update(Graphics g) or
paint(Graphics g); the Applet calls these directly when it first
starts, and when the applet is covered and uncovered. So these two methods also
have to check okToRun. Since this applet isn't supposed to do anything
until you click the Start button, okToRun must be set to
false before starting this Thread.
The final sequence (in the controller) looks like this:
// Get everything set up for the view BEFORE starting its Thread view.alive = true; view.okToRun = false; // Give the view its own thread in which to do the animation animation = new Thread(view); animation.start();
Here are some race bugs that I've had and (hopefully) fixed with this program:
If you see one of these problems, maybe you have a race condition.
There is no simple solution for race conditions. They can all be solved by careful thought, but it isn't easy, and there are a lot of books on the subject. The important thing to keep in mind is that when you start a new Thread, you cannot know whether the current Thread will continue for a while, or whether the new Thread will take over immediately. Your own computer won't always do the same thing. One thing that helps is to get everything initialized and ready for the new Thread before you start it.
|
|
|