Introduction to Computer Graphics and Java Programming for Artists


School of the Museum of Fine Arts ::: Continuing Education

George Aroush, instructor


Lecture Thirteen -- A look at Java's AWT and Events


 

Readings

  1. The handouts named:
  2. Sample Programs in Batch Thirteen:

 

Exercises

    Do before our next lecture

  1. None

Back to the top of this page. Back to main Index

 

Working with Java's AWT and Events

What is AWT?

AWT stands for "Abstract Windwoing Toolket". It's main purpose is to provide the Java programmer the means needed to generate the UI (User Interface) for their application which will communicate with the user. We have been using Java's AWT for a while now but in a limited way. Our main use of it has been in the area of drawing lines, rectangles, circles, polygons, etc. But AWT is much more capable than those simple drawing modes. It provides the Java programmer with the means to display buttons, check-boxes, radio-buttons, menus, etc., and all the familier UI that we see under Windows, the Mac or other Graphical OS.

Why use AWT?

Assume we want to provide a button in our Applete that a user will click on when they are done with selection. Using the simple drawing tools that Java provides we can produce a button and give it the effect that we want when the user clicks on it. Here is an example of how to draw the button:

import java.awt.*;
import java.applet.*;
      
public class ButtonApplet extends Applet
{
    public void paint(Graphics g)
    {
        g.drawString("Cancel Button", 10, 15);
        g.draw3DRect(0, 0, 100, 20, true);
    }
}

This puts a box around the string. The issue here is that all the numbers are hard-coded and are based on pixels, so on some machines the box will fit nicely around the string and on others it will probably be off, because fonts will be different on different machines.

Making a button

Making a button is quite simple: we just call the Button constructor with the label we want on the button. (We can also use the default constructor if we want a button with no label, but this is not very useful.) Usually we'll want to create a variable for the button so you can refer to it later.

The Button is a component, like its own little window, that will automatically get repainted as part of an update. This means that we don't explicitly paint a button or any other kind of control (in the above example where we draw our own button we do have to re-draw it ourself;) we simply place them on the form and let them automatically take care of painting themselves. So to place a button on a form we override init( ) instead of overriding paint( ):

import java.awt.*;
import java.applet.*;
     
public class ButtonOne extends Applet
{
    Button  b1 = new Button("Button 1");
    Button  b2 = new Button("Button 2");

    public void init()
    {
        add(b1);
        add(b2);
    }
}

As the examples shows, it's not enough to create the Button (or any other control). We must also call the Applet add( ) method to cause the button to be placed on the applet's form. This seems a lot simpler than it is, because the call to add( ) actually decides, implicitly, where to place the control on the form. Controlling the layout of a form is examined later.

Capturing an event

We'll notice that if we compile and run the applet above, nothing happens when we press the buttons. This is where we must step in and write some code to determine what will happen. The basis of event-driven programming, which comprises a lot of what a GUI is about, is tying events to code that responds to those events.

Before objects, the typical approach to handling events was the "giant switch statement." Each event would have a unique integer value and inside the master event handling method you'd write a switch on that value.

The AWT in Java 1.0 doesn't use any object-oriented approach. Neither does it use a giant switch statement that relies on the assignment of numbers to events. Instead, we must create a cascaded set of if statements. What we're trying to do with the if statements is detect the object that was the target of the event. That is, if we click on a button, then that particular button is the target. Normally, that's all we care about - if a button is the target of an event, then it was most certainly a mouse click and we can continue based on that assumption. However, events can contain other information as well. For example, if we want to find out the pixel location where a mouse click occurred so we can draw a line to that location (see previouse weeks lectures,) the Event object will contain the location.

The Java 1.0 AWT method where our cascaded if statement resides is called action( ). Although the whole Java 1.0 Event model has been deprecated in Java 1.1, it is still widely used for simple applets and in systems that do not yet support Java 1.1, so it is recommend that we become comfortable with it, including the use of the following action() method approach.

action( ) has two arguments: the first is of type Event and contains all the information about the event that triggered this call to action( ). For example, it could be a mouse click, a normal keyboard press or release, a special key press or release, the fact that the component got or lost the focus, mouse movements, or drags, etc. The second argument is usually the target of the event, which we'll often ignore. The second argument is also encapsulated in the Event object so it is redundant as an argument.

The situations in which action( ) gets called are extremely limited: When we place controls on a form, some types of controls (buttons, check boxes, drop-down lists, menus) have a "standard action" that occurs, which causes the call to action( ) with the appropriate Event object. For example, with a button the action( ) method is called when the button is pressed and at no other time. Usually this is just fine, since that's what we ordinarily look for with a button. However, it's possible to deal with many other types of events via the handleEvent( ) method as we will see later in this lecture.

The previous example can be extended to handle button clicks as follows:

import java.awt.*;
import java.applet.*;

public class Button2 extends Applet
{
    Button  b1 = new Button("Button 1");
    Button  b2 = new Button("Button 2");

    public void init()
    {
        add(b1);
        add(b2);
    }

    public boolean action(Event evt, Object arg)
    {
        if(evt.target.equals(b1))
            getAppletContext().showStatus("Button 1");
        else
        { 
            if(evt.target.equals(b2))
                getAppletContext().showStatus("Button 2");
            else     // Let the base class handle it:
                return (super.action(evt, arg));
        }

        return (true); // We've handled it here
   }
}

In this example to see what the target is, we ask the Event object what its target member is and then use the equals() method to see if it matches the target object handle we're interested in. When we've written handlers for all the objects we're interested in we must call super.action(evt, arg) in the else statement at the end, as shown above. Remember from ealier lecture(s) that our overridden method is called instead of the base class version - action() in this case. However, the base-class version (the original action() method) contains code to handle all of the cases that we're not interested in, and it won't get called unless we call it explicitly. The return value indicates whether we've handled it or not, so if we do match an event we should return true, otherwise return whatever the base-class event() returns.

For this example, the simplest action is to print what button is pressed. Some systems allow us to pop up a little window with a message in it, but applets discourage this. However, we can put a message at the bottom of the Web browser window on its status line by calling the Applet method getAppletContext() to get access to the browser and then showStatus() to put a string on the status line. We can print out a complete description of an event the same way, with getAppletContext().showStatus(evt + "" ). (The empty String forces the compiler to convert evt to a String.) Both of these reports are really useful only for testing and debugging since the browser might overwrite our message.

Text fields

A TextField is a one line area that allows the user to enter and edit text. TextField is inherited from TextComponent, which lets us select text, get the selected text as a String, get or set the text, and set whether the TextField is editable, along with other associated methods that we can find in our online reference. The following example demonstrates some of the functionality of a TextField; we can see that the method names are fairly obvious:

import java.awt.*;
import java.applet.*;
public class TextField1 extends Applet
{
    TextField   t = new TextField("Starting text", 30);
    Button      b1 = new Button("Get Text");
    Button      b2 = new Button("Set Text");
    String      s = new String();

    public void init()
    {
        add(b1);
        add(b2);
        add(t);
    }
    
    public boolean action (Event evt, Object arg)
    {
        if(evt.target.equals(b1))
        {
            getAppletContext().showStatus(t.getText());
            s = t.getSelectedText();
            if(s.length() == 0) s = t.getText();
                t.setEditable(true);
        }
        else
            if(evt.target.equals(b2))
            {
                t.setText("Inserted by Button 2: " + s);
                t.setEditable(false);
            }
            else    // Let the base class handle it:
                return (super.action(evt, arg));

        return (true); // We've handled it here
    }
}

There are several ways to construct a TextField; the one shown here provides an initial string and sets the size of the field in characters.

Pressing button 1 either gets the text we've selected with the mouse or it gets all the text in the field and places the result in String s. It also allows the field to be edited. Pressing button 2 puts a message and s into the text field and prevents the field from being edited (although we can still select the text). The editability of the text is controlled by passing setEditable( ) a true or false.

Text areas

A TextArea is like a TextField except that it can have multiple lines and has significantly more functionality. In addition to what we can do with a TextField, we can append text and insert or replace text at a given location. It seems like this functionality could be useful for TextField as well, so it's a little confusing to try to detect how the distinction is made. We might think that if we want TextArea functionality everywhere we can simply use a one line TextArea in places where we would otherwise use a TextField. In Java 1.0, we also got scroll bars with a TextArea even when they weren't appropriate; that is, we got both vertical and horizontal scroll bars for a one line TextArea. In Java 1.1 this was remedied with an extra constructor that allows us to select which scroll bars (if any) are present. The following example shows only the Java 1.0 behavior, in which the scrollbars are always on:

import java.awt.*;
import java.applet.*;

public class TextArea1 extends Applet
{
    Button b1 = new Button("Text Area 1");
    Button b2 = new Button("Text Area 2");
    Button b3 = new Button("Replace Text");
    Button b4 = new Button("Insert Text");
    TextArea t1 = new TextArea("t1", 1, 30);
    TextArea t2 = new TextArea("t2", 4, 30);

    public void init()
    {
        add(b1);
        add(t1);
        add(b2);
        add(t2);
        add(b3);
        add(b4);
    }

    public boolean action (Event evt, Object arg)
    {
        if(evt.target.equals(b1))
            getAppletContext().showStatus(t1.getText());
        else
            if(evt.target.equals(b2))
            {
                t2.setText("Inserted by Button 2");
                t2.appendText(": " + t1.getText());
                getAppletContext().showStatus(t2.getText());
            }
            else
                if(evt.target.equals(b3))
                {
                    String s = " Replacement ";
                    t2.replaceText(s, 3, 3 + s.length());
                }
                else
                    if(evt.target.equals(b4))
                        t2.insertText(" Inserted ", 10);
                    else    // Let the base class handle it:
                        return (super.action(evt, arg));

        return (true); // We've handled it here
   }
}

There are several different TextArea constructors, but the one shown here gives a starting string and the number of rows and columns. The different buttons show getting, appending, replacing, and inserting text.

Labels

A Label does exactly what it sounds like it should: places a label on the form. This is particularly important for text fields and text areas that don't have labels of their own, and can also be useful if we simply want to place textual information on a form. We can, as shown in the first example, use drawString() inside paint() to place text in an exact location. When we use a Label it allows us to (approximately) associate the text with some other component via the layout manager (which will be discussed next week).

With the constructor we can create a blank label, a label with initial text in it (which is what we'll typically do), and a label with an alignment of CENTER, LEFT, or RIGHT (static final ints defined in class Label). We can also change the label and its alignment with setText() and setAlignment(), and if we've forgotten what we've set these to we can read the values with getText() and getAlignment(). This example shows what we can do with labels:

import java.awt.*;
import java.applet.*; 

public class Label1 extends Applet
{
    TextField t1 = new TextField("t1", 10);
    Label labl1 = new Label("TextField t1");
    Label labl2 = new Label(" ");
    Label labl3 = new Label(" ", Label.RIGHT);
    Button b1 = new Button("Test 1");
    Button b2 = new Button("Test 2");

    public void init()
    {
        add(labl1);
        add(t1);
        add(b1);
        add(labl2);
        add(b2);
        add(labl3);
    }

    public boolean action (Event evt, Object arg)
    {
        if(evt.target.equals(b1))
            labl2.setText("Text set into Label");
        else
            if(evt.target.equals(b2))
            {
                if(labl3.getText().trim().length() == 0)
                    labl3.setText("labl3");
                if(labl3.getAlignment() == Label.LEFT)
                    labl3.setAlignment(Label.CENTER);
                else
                    if(labl3.getAlignment()==Label.CENTER)
                        labl3.setAlignment(Label.RIGHT);
                    else
                        if(labl3.getAlignment() == Label.RIGHT)
                            labl3.setAlignment(Label.LEFT);
            }
            else 
                return (super.action(evt, arg));

        return (true);
    }
}

The first use of the label is the most typical: labeling a TextField or TextArea. In the second part of the example, a bunch of empty spaces are reserved and when we press the "Test 1" button setText() is used to insert text into the field. Because a number of blank spaces do not equal the same number of characters (in a proportionally-spaced font) we'll see that the text gets truncated when inserted into the label.

The third part of the example reserves empty space, then the first time you press the "Test 2" button it sees that there are no characters in the label (since trim() removes all of the blank spaces at each end of a String) and inserts a short label, which is initially left-aligned. The rest of the times we press the button it changes the alignment so we can see the effect.

We might think that we could create an empty label and then later put text in it with setText(). However, we cannot put text into an empty label - presumably because it has zero width - so creating a label with no text seems to be a useless thing to do. In the example above, the "blank" label is filled with empty spaces so it has enough width to hold text that's placed inside later.

Similarly, setAlignment() has no effect on a label that we'd typically create with text in the constructor. The label width is the width of the text, so changing the alignment doesn't do anything. However, if we start with a long label and then change it to a shorter one we can see the effect of the alignment.

These behaviors occur because of the default layout manager that's used for applets, which causes things to be squished together to their smallest size. Layout managers will be covered next week, when we'll see that other layouts don't have the same effect.

Check boxes

A check box provides a way to make a single on-off choice; it consists of a tiny box and a label. The box typically holds a little 'x' (or some other indication that it is set) or is empty depending on whether that item was selected.

We'll normally create a Checkbox using a constructor that takes the label as an argument. We can get and set the state, and also get and set the label if we want to read or change it after the Checkbox has been created. Note that the capitalization of Checkbox is inconsistent with the other controls, which could catch us by surprise since we might expect it to be "CheckBox."

Whenever a Checkbox is set or cleared an event occurs, which we can capture the same way we do a button. The following example uses a TextArea to enumerate all the check boxes that have been checked:

import java.awt.*;
import java.applet.*;

public class CheckBox1 extends Applet
{
    TextArea t = new TextArea(6, 20);
    Checkbox cb1 = new Checkbox("Check Box 1");
    Checkbox cb2 = new Checkbox("Check Box 2");
    Checkbox cb3 = new Checkbox("Check Box 3");

    public void init()
    {
        add(t);
        add(cb1);
        add(cb2);
        add(cb3);
    }

    public boolean action (Event evt, Object arg)
    {
        if(evt.target.equals(cb1))
            trace("1", cb1.getState());
        else
            if(evt.target.equals(cb2))
                trace("2", cb2.getState());
            else
                if(evt.target.equals(cb3))
                    trace("3", cb3.getState());
                else 
                    return (super.action(evt, arg));

        return (true);
    }

    void trace(String b, boolean state)
    {
        if(state)
            t.appendText("Box " + b + " Set\n");
        else
            t.appendText("Box " + b + " Cleared\n");
    }
}

The trace() method sends the name of the selected Checkbox and its current state to the TextArea using appendText() so we'll see a cumulative list of the checkboxes that were selected and what their state is.

Radio buttons

The concept of a radio button in GUI programming comes from pre-electronic car radios with mechanical buttons: when we push one in, any other button that was pressed pops out. Thus it allows us to force a single choice among many.

The AWT does not have a separate class to represent the radio button; instead it reuses the Checkbox. However, to put the Checkbox in a radio button group (and to change its shape so it's visually different from an ordinary Checkbox) we must use a special constructor that takes a CheckboxGroup object as an argument.

A CheckboxGroup has no constructor argument; its sole reason for existence is to collect some Checkboxes into a group of radio buttons. One of the Checkbox objects must have its state set to true before we try to display the group of radio buttons; otherwise you'll get an exception (error) at run time. If we try to set more than one radio button to true then only the final one set will be true.

Here's a simple example of the use of radio buttons. Note that we capture radio button events like all others:

import java.awt.*;
import java.applet.*; 

public class RadioButton1 extends Applet
{
    TextField t = new TextField("Radio button 2", 30);
    CheckboxGroup g = new CheckboxGroup();
    Checkbox cb1 = new Checkbox("one", g, false);
    Checkbox cb2 = new Checkbox("two", g, true);
    Checkbox cb3 = new Checkbox("three", g, false);

    public void init()
    {
        t.setEditable(false);
        add(t); 
        add(cb1); add(cb2); add(cb3); 
    }

    public boolean action (Event evt, Object arg)
    {
        if(evt.target.equals(cb1))
            t.setText("Radio button 1");
        else
            if(evt.target.equals(cb2))
                t.setText("Radio button 2");
            else
                if(evt.target.equals(cb3))
                    t.setText("Radio button 3");
                else 
                    return (super.action(evt, arg));
        return true;
    }
}

To display the state, a text field is used. This field is set to non-editable because it's used only to display data, not to collect it. This is shown as an alternative to using a Label. Notice the text in the field is initialized to "Radio button 2" since that's the initial selected radio button.  We can have any number of CheckboxGroups on a form.

Drop-down lists

Like a group of radio buttons, a drop-down list is a way to force the user to select only one element from a group of possibilities. However, it's a much more compact way to accomplish this, and it's easier to change the elements of the list without surprising the user. (We can change radio buttons dynamically, but that tends to be visibly jarring).

Java's Choice box is not like the combo box in Windows, which lets us select from a list or type in our own selection. With a Choice box we choose one and only one element from the list. In the following example, the Choice box starts with a certain number of entries and then new entries are added to the box when a button is pressed. This allows us to see some interesting behaviors in Choice boxes:

import java.awt.*;
import java.applet.*; 

public class Choice1 extends Applet
{
    String[] description = { "Ebullient", "Obtuse",
                             "Recalcitrant", "Brilliant", "Somnescent",
                             "Timorous", "Florid", "Putrescent" };
    TextField t = new TextField(30);
    Choice c = new Choice();
    Button b = new Button("Add items");
    int count = 0;

    public void init()
    {
        t.setEditable(false);

        for(int i = 0; i < 4; i++)
            c.addItem(description[count++]);

        add(t);
        add(c);
        add(b);
    }

    public boolean action (Event evt, Object arg)
    {
        if(evt.target.equals(c))
            t.setText("index: " + c.getSelectedIndex() + " " + (String)arg);
        else
            if(evt.target.equals(b))
            {
                if(count < description.length)
                    c.addItem(description[count++]);
            } 
            else 
                return (super.action(evt, arg));

        return (true);
   }
}

The TextField displays the "selected index," which is the sequence number of the currently selected element, as well as the String representation of the second argument of action(), which is in this case the string that was selected.

When we run this applet, pay attention to the determination of the size of the Choice box: in Windows, the size is fixed from the first time we drop down the list. This means that if we drop down the list, then add more elements to the list, the elements will be there but the drop-down list won't get any longer (we can scroll through the elements). However, if we add all the elements before the first time the list is dropped down, then it will be sized correctly. Of course, the user will expect to see the whole list when it's dropped down, so this behavior puts some significant limitations on adding elements to Choice boxes.

List boxes

List boxes are significantly different from Choice boxes, and not just in appearance. While a Choice box drops down when we activate it, a List occupies some fixed number of lines on a screen all the time and doesn't change. In addition, a List allows multiple selection: if we click on more than one item the original item stays highlighted and you can select as many as you want. If you want to see the items in a list, you simply call getSelectedItems( ), which produces an array of String of the items that have been selected. To remove an item from a group you have to click it again.
A problem with a List is that the default action is double clicking, not single clicking. A single click adds or removes elements from the selected group and a double click calls action( ). One way around this is to re-educate your user, which is the assumption made in the following program:

import java.awt.*;
import java.applet.*;

public class List1 extends Applet
{
    String[] flavors = { "Chocolate", "Strawberry",
                         "Vanilla Fudge Swirl", "Mint Chip", 
                         "Mocha Almond Fudge", "Rum Raisin", 
                         "Praline Cream", "Mud Pie" };
    // Show 6 items, allow multiple selection:
    List lst = new List(6, true);
    TextArea t = new TextArea(flavors.length, 30);
    Button b = new Button("test");
    int count = 0;

    public void init()
    {
        t.setEditable(false);
        for(int i = 0; i < 4; i++)
            lst.addItem(flavors[count++]);
  
        add(t);
        add(lst);
        add(b);
    }

    public boolean action (Event evt, Object arg)
    {
        if(evt.target.equals(lst))
        {
            t.setText("");
            String[] items = lst.getSelectedItems();

            for(int i = 0; i < items.length; i++)
                t.appendText(items[i] + "\n");
        }
        else
            if(evt.target.equals(b))
            {
                if(count < flavors.length)
                    lst.addItem(flavors[count++], 0);
            }
            else 
                return (super.action(evt, arg));
  
        return true;
    }
}

When we press the button it adds items to the top of the list (because of the second argument 0 to addItem()). Adding elements to a List is more reasonable than the Choice box because users expect to scroll a list box (for one thing, it has a built-in scroll bar) but they don't expect to have to figure out how to get a drop-down list to scroll, as in the previous example.

However, the only way for action( ) to be called is through a double-click. If we need to monitor other activities that the user is doing on our List (in particular, single clicks) we must take an alternative approach which we will examine in an upcoming lecture.


Back to the top of this page. Back to main Index

 

Sample Programs -- Batch Thirteen


Back to the top of this page. Back to main Index.