Kaolin Fire with GUD Issues 0 through 5

kaolin fire presents :: software :: tutorial :: Java AWT



Outline:
  1. Intro
    • meta-discussion
  2. External Representation
    • what to think about
  3. Internal Representation
    • paint(Graphics)
    • update(Graphics)
    • repaint()
    • "double buffering"
    • the Graphics class
    • layout managers
  4. The Java 1.1 Event model
    • AWTEvent
    • AWTEventMulticaster
    • EventListener
  5. Our Example: Mr. Smiley

** 0) INTRO **

The AWT is the main reason I use java, so I think it's a pretty important thing to understand if you're going to use it as well. There are three things to keep in mind when making a GUI (Graphical User Interface):
    1) External representation
      what it's actually going to look like.
    2) Internal representation
      what's going on in there?
And, most importantly if you're going to make your graphics actually user interfaceable...
    3) The Java 1.1 Event model
      how objects pass messages around

** 1) EXTERNAL REPRESENTATION **

So, let's take these in order, and see where we get to. The first thing you're going to want to do when designing a GUI is decide what you want it to look like, and how you want it to act. Let's focus on what you want it to look like. For application programming (as opposed to applets) the first step for any graphics is to create a Frame. This is the thing in windows that has the 'x' kill button, and the minimize and maximize buttons.

In order to do any graphics with an application, you HAVE to create a Frame.

The Frame takes one optional argument, and that is a String, which will be displayed as the Frame's title. You can change the title of a Frame at any time by using it's setTitle(String) method.

In general, I find it useful to call addNotify() on the Frame as soon as I create one. This creates the "peer hierarchy" that allows all the graphics to go on.... making the frame visible will automatically call addNotify() for you, but sometimes (well, I've never found it not to be the case) you want to fiddle with the graphics before actually displaying the frame.

Beyond the Frame, Java gives you pretty much complete leeway. You can use built-in components to piece together an interface, or you can implement your own. Your interface can be one gigantic painted image, or it can be three dozen components and buttons and slide-bars and whatnot. Java 1.2 adds a lot more toys, but I haven't made that transition yet -- personally, I think the base distribution is getting too large... And you really don't need them.

For our example, let's have a yellow smiley-face on a black background, with one buttons below it, that toggles whether the smiley-face smiles or frowns.

** 2) INTERNAL REPRESENTATION **

Three or four (or five?) (six???) things of utmost importance:

  1. paint(Graphics) method (when is it called and what is it for?)
  2. update(Graphics) method (when is it called and what is it for?)
  3. repaint() method (what is it for and when should I call it?)
  4. "double buffering" (what is it? why should i use it?)
  5. the Graphics class (how do I get it and how do I use it?)
  6. Layout managers (who? what? when? where? why?)

If you want anything on the screen other than boxes and text... then you're going to need to create a class for whatever is going to have the graphics or drawing. These methods are also KEY for applets, as applets are generally nothing but painted images and snifty features.

Well, I have this thing for squeezing as much as I can into one class. So I usually make the class that I'm writing extend Frame. Then I put the paint(Graphics) and update(Graphics) into it, and I'm all set. paint(Graphics) and update(Graphics) are actually part of the Component class, so really as long as you add some Component with those methods into the Frame, then it's all good... but I'm hoping to give you a jump start into the AWT, not a comprehensive lecture, because there's no way I could do that even in three hours.

paint(Graphics) and update(Graphics) are called by a system thread. This thread is always running, doing stuff, and one of the things it does is monitor whether something needs to be painted. For instance, you have a beautiful smiley-face painted in a Frame, and then someone comes up and completely covers it up with a Minesweeper window. A few minutes later, they tire of the game, and move it off of your Frame... and your smiley-face is gone! This is when the system calls your paint(Graphics) method... it's your job to put it back.

The update(Graphics) method is just like the paint(Graphics) in many ways. It's called when the background is NOT cleared, but something else caused your graphics to fwibble. Maybe you're even calling it yourself. This method defaults to a simple routine that clears the background and calls your paint method. In many cases, that's just fine and dandy to let it do. That's what we'll do with our example.

The repaint() you'll almost invariably call yourself. This means you've made some change somewhere that ought to affect what's being displayed on the screen, and you want the system to realise that and call your update(Graphics) or your paint(Graphics) method, whichever is appropriate.

Double Buffering is a technique that is supposed to eliminate the dreaded 'flicker' of redrawing. The flicker occurs when you're drawing straight onto the Graphics context being displayed. It happens because you're having to wait for the proper interrupts for the monitor, slowly doing the paint, then doing another one, and another, and each of these have to wait for more interrupts and such. (I think, I could be wrong) In any case, flickering occurs when you have multiple commands to do painting and such on a Component that is currently visible. With double buffering, you create an Image, do all your modifications to that image, and then paint the image as a whole to the screen. This is considered good programming practice. Everyone should do it.

Finally, before we can begin... the Graphics class. The Graphics class has a plethora of commands. A brief list of the ones I use commonly:

    draw3DRect, drawArc, drawOval, drawRect, drawRoundRect, drawLine, fill3DRect, fillArc, fillOval, fillRect, fillRoundRect

The arguments these take are all in any reference book, or in the Javadocs, so I'll only go into detail with the ones we need for our example.

The graphics context is simply given to you in the paint(Graphics) and update(Graphics) methods, but how do you get one outside of those methods? For instance, how would we crete an image with a smiley-face on it that we can later paint into our frame?

Everything that inherits from Component no matter how distant a relation has a 'graphics context.' In order to do any painting on an object, you need to get its graphics context. Note: if the component's peer doesn't exist, you can't get its graphics context! This is why we need that addNotify().

Another important thing if you're going to use any prebuilt components whatsoever is the LayoutManager class. The layout managers are one way of piecing together what goes on the screen. Components default to the FlowLayout, so I'll touch on that a bit in the example. Other layout managers include GridLayout, BorderLayout, CardLayout, and GridBagLayout. I personally avoid using any of these whenever I can.

** 3) THE JAVA 1.1 EVENT MODEL **

AWTEvents come in many flavours: ActionEvent, AdjustmentEvent, ComponentEvent, ItemEvent and TextEvent. Then there is InputEvent which extends ComponentEvent and MouseEvent and KeyEvent that extend InputEvent. And there are even more events than these. Just a reminder: Events are objects just like everything else. Buttons just fire ActionEvents and events all get treated in pretty much the same manner, so I'll try to describe what's going on inside of a Button. (Note: the Event class itself was a Java 1.0 abberation and worked very differently)

The Java 1.1 event model relies on two main things besides the events themselves. These are the EventListener interface (and descendants), and the AWTEventMulticaster. As far as I'm concerned, all we need to know about the multicaster is that you can use it to "glue" EventListeners together into one EventListener for the purposes of firing an AWTEvent. (Something to the effect of storing a Vector of EventListeners)

The EventListener interface means that you promise to have a certain method that what you are listing to can call when it wants to fire an AWTEvent.

The ActionListener interface only has one method you need to implement, and that is actionPerformed(ActionEvent). This is how the things you are listening to you can "fire" events. They simply call this method. The way you let them know that you want to listen to them is by calling their addListener(Listener), or in this case addActionListener(ActionListener) method.

** 4) OUR EXAMPLE: MR. SMILEY **

I'm going to break this into two classes to try to make what's happening a little easier to see. The first class will have our main(String[] argv) method, and will put everything together. Then we will have a class SmileyFace which extends Component.

Smiley.java (download the source)
import java.awt.*;
  // so we can use Button and Frame
import java.awt.event.*;
  // for ActionListner and ActionEvent

public class Smiley implements ActionListener {
  // implements ActionListner so we can respond to a button press

  Frame f;
  // the basis of any Application's graphics
  SmileyFace sf;
  // our smileyface!
  Button toggle;
  //What we use to get input from the user

  public static void main(String[] argv) {
    new Smiley();
    // we use the main to make an instantiation of ourselves so that
    // we have a "this" to give when we call addActionListener
  }

  public Smiley() {
    f=new Frame("Mr. Smiley");
    // make the frame and give it a title...
    f.addNotify();
    // create the frame's peers... 
    sf=new SmileyFace();
    // make our smiley!
    toggle=new Button("Toggle");
    // make a new button with "Toggle" printed on it
    toggle.addActionListener(this); 
    // tell toggle to let us know when it gets clicked
    f.setSize(640,480);
    // male the frame a standard (small-ish) size
    f.add(toggle,BorderLayout.SOUTH);
    // add our button to the bottom of the frame...
    f.add(sf,BorderLayout.CENTER);
    // add out smiley to the middle of the frame (sorta)
    f.setVisible(true); 
    // makes our frame and anything in it, VISIBLE.
  }

  public void actionPerformed(ActionEvent ae) {
    sf.toggle();
    // here I'm being a bit lazy... since the ONLY thing we're listening
    // to is our button, I don't really need to distinguish between what's
    // what... so I'll assume I got what I want and I'll just tell our smiley
    // face to toggle... (a method at this point I'm just assuming it has)
  }
}
SmileyFace.java (download the source)
import java.awt.*; 
  // so we can use Component and Graphics....

public class SmileyFace extends Component { 
  // extends component so we can override the paint and update methods.

  boolean smile=true;  
  // we ought to know whether we're smiling or not

  Image img; 
  // and this is for the double buffering

  public SmileyFace() {
    setSize(400,400);
    // this is so the layout manager can know what size we WANT to be...
  }

  public void toggle() {
    // we promised we'd have this method available to the public...
    smile=!smile;
    // toggle whether we're smiling or not...
    paintSmile();
    // presume this will paint a smile the way we want it to....
  }

  public void paint(Graphics g) {
    // provide the System thread a way to let us know when to paint...
    if (img != null) {
      // if we've created the image, it won't be null...
      g.drawImage(img,0,0,null);
      // paint the image to the screen... the null is for ImageObserver which
      // is for more complicated images, like progressive .jpg or interlaced
      // .gif
    } else {
      //if we HAVEN'T created the image, it's time to....
      img=createImage(400,400);
      // see?
      paintSmile();
      // and here again we presume this will paint the smile on the image....
    }
  }

  private void paintSmile() {
    // here we finally try to paint the smile on the image
    Graphics g=img.getGraphics();
    // get the graphics context from the image... if the image was null or the
    // image's peers weren't created at this point, we'd get errors... but the
    // rest of the code has already made sure of this.  so, onwards....
    g.setColor(Color.black);
    // the background of our smiley should be black
    g.fillRect(0,0,400,400);
    // paint a big fat filled rectangle of black
    g.setColor(Color.yellow);
    // the smiley itself should be yellow
    g.fillOval(0,0,400,400);
    // paint a big fat oval of yellow... note that the coordinates are not
    // center based!  You define a rectangle and it fills that rectangle with
    // and oval....
    g.setColor(Color.black);
    // and back to black for the eyes and mouth.
    g.fillOval(100,100,50,50);
    // left eye
    g.fillOval(250,100,50,50);
    // right eye
    if (smile) g.drawArc(150,250,100,100,180,180);
    // if we're smiling draw a smile
    else g.drawArc(150,250,100,100,0,180);
    // else draw a frown
    repaint();
    // let the system thread know that we want to repaint...
    // system thread will call update() which defaults to clearing the
    // background and calling paint() which will paint our image on the
    // screen!
  }


}



I am soooo fake pre-loading this image so the navigation doesn't skip while loading the over state.  I know I could use the sliding doors technique to avoid this fate, but I am too lazy.