Now that we've learned some basics and have gotten our introduction sequence written, lets jump into some window programming. In this lesson we will learn how to create a new window and paint a little red box. It's not very exciting, but it's a start. It will be similar to the one to the one shown here, but will actually come up in its own window (rather than being embedded in a web page). The applet shown here is just an example of what your program will look like.
|
This is meant to be a replacement when we do things (javascript menus) that might get hidden behind the applet.
|
The Program that doesn't die
Feedback
This is about as simple a window application as you can get, but it has significant problems.
public class Stakeout {
public static void main (String args[]) throws java.io.IOException {
System.out.println ("Welcome to Stakeout!");
System.out.println ("Creating the Stakeout window...");
javax.swing.JFrame window = new javax.swing.JFrame ();
window.setTitle ("Stakeout Window Title");
window.setSize (340, 370);
window.setVisible (true);
System.out.println ("Game over!");
}
}
What's happening
We already know what those println's are doing. But the
JFrame line is different. JFrame is the class that implements the basic window that we're going to use in our applications. JFrame lives in the java package called "javax.swing". A package is just a way to group a bunch of associated classes together. So to create a new instance of this class we reference the full name of the class (including the package name) and use the "new" operator to (logically) create a new one. Then in the following three lines, we call the funcitons of this class on the instance in order to make the window appear the way that we want (setting the size, title and visibility). When we run the application it looks great. JFrame gives us the ability to move the window around and resize it. But there's a problem...
Killing the app
We look back at our console and we notice that it already says "Game over!". Then when we close the window by clicking on the little 'x' in the upper right hand corner, it disappears as expected, but we go back to our console window again and we notice that it's completely stuck. This will take some explaining. As in our previous programs, execution starts from the beginning of the "main" function and then goes down until it reaches the end of the main function at which point it exits the program and the prompt is freed up again for other things. But actually, it isn't that simple. The program doesn't just exit when it gets to the end of the main function. First it waits for all the "threads" in our program to finish and then it exits. It's just that in our other programs we didn't have any other threads than the "main" thread. In this program, as soon as we create the JFrame, a new thread is created to handle the events as they happen to that window. Every time we move the mouse around, when we click in the window, when we change the size and position of the window there is a bunch of code that's being run to magically make all that stuff work right. Simplistically it looks like this:
while (1) {
event = GetNextEvent ();
if (event.type == size_change) {
ChangeWindowSize (event);
}
else if (event.type == close_window_event) {
CloseWindow (event);
windowListener.windowClosing (event);
}
...
}
All of this event handling is happening at the same time as our main function is continuing to execute the rest of the program. Since there's not much for it to do, it very quickly gets to the end of the "main" function, prints out the "Game over!" message and then waits for the JFrame thread to finish. Notice that there's no end condition for our JFrame's while loop. It just keeps on processing input. If it get's the "close_window_event" (which happens when we kill the window) it calls the windowListener's windowClosing function. But we haven't established a window listener and there isn't a default windowListener. So this ends up doing nothing and the JFrame goes back to processing events and the program never exits. The only way to kill this application is to press Ctrl-C on the console window or kill it in the Task Manager. This is not very user friendly. We're going to have to fix this or no one will want to run our program. We solve this by creatig a WindowListener and subscribing it to our windows events.
WindowListener
WindowListener is an interface that a class implements. That basically means it's an agreement that a class makes to implement certain functions. As you can see from the documentation, there are seven functions that WindowListeners have to implement. So to solve our never-ending program problem, we need to pass an instance of a class that implements the WindowListener interface to our JFrame. We will create our own special class for this purpose.
class StakeoutWindowAdapter extends java.awt.event.WindowAdapter {
public void windowClosing (java.awt.event.WindowEvent e) {
System.out.println ("Game over!");
System.exit (0);
}
}
This StakeoutWindowAdapter class doesn't directly implement the WindowListener interface. Instead it extends the
WindowAdapter class which in turn implements the WindowListener interface. "Extending" or often called inheriting from another class gives the child class all the functionality of the parent class. Then the child class can override the functionality of the parent class by creating functions with the same name. When such a function is called, the version in the child class will take preference. If no such function exists in the child class, it will be searched for in the parent class(es). Fortunately WindowAdapter implements all seven functions from the WindowListener interface. So all we have to do is override the ones that we are interested in. Right now we are only interested in knowing when the window is closing so we only need to override the windowClosing function. We move our "Game over!" message here and then we force the program to exit. You might wonder why this isn't done by default. Many programs consist of more than one window. So killing the whole application when one of the windows closed would not be the right behavior. Fortunately we only have one window so we know that when it is closed, it's time to end the program.
Simply creating a WindowListener class will not suffice. We have to actually create an instance of it and subscribe that instance to listen to events from our JFrame. Here's the whole program:
class StakeoutWindowAdapter extends java.awt.event.WindowAdapter {
public void windowClosing (java.awt.event.WindowEvent e) {
System.out.println ("Game over!");
System.exit (0);
}
}
public class Stakeout {
public static void main (String args[]) throws java.io.IOException {
System.out.println ("Welcome to Stakeout!");
System.out.println ("Creating the Stakeout window...");
javax.swing.JFrame window = new javax.swing.JFrame ();
StakeoutWindowAdapter adapter = new StakeoutWindowAdapter ();
window.addWindowListener (adapter);
window.setTitle ("Stakeout Window Title");
window.setSize (340, 370);
window.setVisible (true);
}
}
Now when we run our program the "Game over!" message will not be displayed until we actually shut down the window and when we do the program will exit and our console will become usable again.
In our program there's a lot of java.awt... stuff repeated all over the place. It's not too bad right now, but this will quickly become tedious to type out in any kind of larger program and it can also be visually distracting. Fortunately Java has a shortcut. If at the top of the program we "import" the packages where the classes we use come from, then we can refer to them in our program simply by using their names.
import java.io.*;
import javax.swing.*;
import java.awt.event.*;
class StakeoutWindowAdapter extends WindowAdapter {
public void windowClosing (WindowEvent e) {
System.out.println ("Game over!");
System.exit (0);
}
}
public class Stakeout {
public static void main (String args[]) throws IOException {
System.out.println ("Welcome to Stakeout!");
System.out.println ("Creating the Stakeout window...");
JFrame window = new JFrame ();
StakeoutWindowAdapter adapter = new StakeoutWindowAdapter ();
window.addWindowListener (adapter);
window.setTitle ("Stakeout Window Title");
window.setSize (340, 370);
window.setVisible (true);
}
}
This only saves us a few keystrokes now, but will make our life much easier later on.
Drawing in the Window
Feedback
Right now all our window does is draw the default grey background on the screen. We want to be able to draw the map (a 300 x 300 pixel black square) and one unit (a 30 x 30 pixel red block). To do this we create a new custom
Component which we add to our JFrame. It's sort of a window inside of a window. Inside this sub-windows we will then draw our map and our unit.
import java.io.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
class StakeoutWindowAdapter extends WindowAdapter {
public void windowClosing (WindowEvent e) {
System.out.println ("Game over!");
System.exit (0);
}
}
class StakeoutComponent extends Component {
public void StakeoutComponent () {
setSize (300, 300);
setVisible (true);
}
public void paint (Graphics canvas) {
canvas.setColor (Color.BLACK);
canvas.fillRect (0, 0, 300, 300);
canvas.setColor (Color.RED);
canvas.fillRect (0, 0, 30, 30);
}
}
public class Stakeout {
public static void main (String args[]) throws IOException {
System.out.println ("Welcome to Stakeout!");
System.out.println ("Creating the Stakeout window...");
JFrame window = new JFrame ();
StakeoutWindowAdapter adapter = new StakeoutWindowAdapter ();
window.addWindowListener (adapter);
window.setTitle ("Stakeout Window Title");
window.setSize (340, 370);
StakeoutComponent component = new StakeoutComponent ();
window.add (component);
window.setVisible (true);
}
}
The
Graphics class (used in StakeoutComponent's paint function) is in the java.awt package so we add a new import statement. We define a new class called StakeoutComponent which inherits from Component and overrides its paint function. In the paint function we use the Graphics object to paint onto the screen. First you have to set the color that you want to paint with, then you tell it what you want to paint. Color.Black and Color.Red are predefined colors in the
Color class. For now, we will restrict ourselves to these predefined colors. Telling it where to draw our rectangles is as easy as specifying the x and the y coordinates and then specifying the width and the height. The x and y coordinates are a little different than what you're used to. They start from the upper-left (0,0). The x-coordinate goes up as you go to the right (typical), but the y-coordinate goes up as you go down (atypical).
The other function in the StakeoutComponent class is its constructor. The constructor is the code that is run when you create a new instance of this class with the "new" operator. The main function in the Stakeout class is pretty much the same except that now we are creating a new instance of our StakeoutComponent and are adding it to the JFrame. Notice that we do this before making the JFrame visible because otherwise it might not get painted initially.
Follow-up exercise
Feedback
Here are some things you can try to tweak:
- Change the color of the background
- Change the color of the unit
- Change the position of the unit
Here are some things which you should try to implement on your own in preparation for the next lesson. Don't worry about actually implementing the feature right now.
- In the startup sequence, prompt the user for a color
- In the startup sequence, prompt the user for the position to draw the unit at
- Add a loop after your startup sequence to continually prompt the user to move the unit around on the screen