HelloJava3: The Button Strikes!

Now we can move on to some fun stuff. HelloJava3 brings us a new graphical interface component: JButton.[4] In this example, we add a JButton component to our application that changes the color of our text each time the button is pressed. The draggable-message capability is still there, too. Our new code looks like this:

    //file: HelloJava3.java
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;

    public class HelloJava3
    {

      public static void main( String[] args ) {
        JFrame frame = new JFrame( "HelloJava3" );
        frame.add( new HelloComponent3("Hello, Java!") );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setSize( 300, 300 );
        frame.setVisible( true );
      }
    }

    class HelloComponent3 extends JComponent
        implements MouseMotionListener, ActionListener
    {
      String theMessage;
      int messageX = 125, messageY = 95;  // Coordinates of the message

      JButton theButton;

      int colorIndex;  // Current index into someColors
      static Color[] someColors = {
        Color.black, Color.red, Color.green, Color.blue, Color.magenta };

      public HelloComponent3( String message ) {
        theMessage = message;
        theButton = new JButton("Change Color");
        setLayout( new FlowLayout() );
        add( theButton );
        theButton.addActionListener( this );
        addMouseMotionListener( this );
      }

      public void paintComponent( Graphics g ) {
        g.drawString( theMessage, messageX, messageY );
      }

      public void mouseDragged( MouseEvent e ) {
        messageX = e.getX();
        messageY = e.getY();
        repaint();
      }

      public void mouseMoved( MouseEvent e ) {}

      public void actionPerformed( ActionEvent e ) {
        // Did somebody push our button?
        if (e.getSource() == theButton)
          changeColor();
      }

      synchronized private void changeColor() {
        // Change the index to the next color, awkwardly.
        if (++colorIndex == someColors.length)
          colorIndex = 0;
        setForeground( currentColor() ); // Use the new color.
        repaint();
      }


      synchronized private Color currentColor() {
        return someColors[colorIndex];
      }
    }

Compile HelloJava3 in the same way as the other applications. Run the example, and you should see the display shown in Figure 2-9. Drag the text. Each time you press the button, the color should change. Call your friends! Test yourself for color blindness!

The HelloJava3 application

Figure 2-9. The HelloJava3 application

What have we added this time? Well, for starters, we have a new variable:

    JButton theButton;

The theButton variable is of type JButton and is going to hold an instance of the javax.swing.JButton class. The JButton class, as you might expect, represents a graphical button, like other buttons in your windowing system.

Three additional lines in the constructor create the button and display it:

    theButton = new JButton("Change Color");
    setLayout( new FlowLayout() );
    add( theButton );

In the first line, the new keyword creates an instance of the JButton class. The next line affects the way our component will be used as a container to hold the button. It tells HelloComponent3 how it should arrange components that are added to it for display—in this case, to use a scheme called a FlowLayout (more on that coming up). Finally, it adds the button to our component, just like we added HelloComponent3 to the content pane of the JFrame in the main() method.

Method Overloading

JButton has more than one constructor. A class can have multiple constructors, each taking different parameters and presumably using them to do different kinds of setup. When a class has multiple constructors, Java chooses the correct one based on the types of arguments used with them. We call the JButton constructor with a String argument, so Java locates the constructor method of the JButton class that takes a single String argument and uses it to set up the object. This is called method overloading. All methods in Java (not just constructors) can be overloaded; this is another aspect of the object-oriented programming principle of polymorphism.

Overloaded constructors generally provide a convenient way to initialize a new object. The JButton constructor we’ve used sets the text of the button as it is created:

    theButton = new JButton("Change Color");

This is shorthand for creating the button and setting its label, like this:

    theButton = new JButton();
    theButton.setText("Change Color");

Components

We have used the terms component and container somewhat loosely to describe graphical elements of Java applications, but these terms are used in the names of actual classes in the java.awt package.

Component is a base class from which all of Java’s GUI components are derived. It contains variables that represent the location, shape, general appearance, and status of the object as well as methods for basic painting and event handling. javax.swing.JComponent extends the base Component class and refines it for the Swing toolkit. The paintComponent() method we have been using in our example is inherited from the JComponent class. HelloComponent is a kind of JComponent and inherits all its public members, just as other GUI components do.

The JButton class is also derived from JComponent and therefore shares this functionality. This means that the developer of the JButton class had methods such as paintComponent() available with which to implement the behavior of the JButton object, just as we did when creating our example. What’s exciting is that we are perfectly free to further subclass components such as JButton and override their behavior to create our own special types of user-interface components. JButton and HelloComponent3 are, in this respect, equivalent types of things.

Containers

The Container class is an extended type of Component that maintains a list of child components and helps to group them. The Container causes its children to be displayed and arranges them on the screen according to a particular layout strategy.

Because a Container is also a Component, it can be placed alongside other Component objects in other Containers in a hierarchical fashion, as shown in Figure 2-10. Our HelloComponent3 class is a kind of Container (by virtue of the JComponent class) and can therefore hold and manage other Java components and containers, such as buttons, sliders, text fields, and panels.

Layout of Java containers (in bold) and components (in italics)

Figure 2-10. Layout of Java containers (in bold) and components (in italics)

In Figure 2-10, the italicized items are Components, and the bold items are Containers. The keypad is implemented as a container object that manages a number of keys. The keypad itself is contained in the GizmoTool container object.

Since JComponent descends from Container, it can be both a component and a container. In fact, we’ve already used it in this capacity in the HelloComponent3 example. It does its own drawing and handles events, just like a component, but it also contains a button, just like a container.

Layout

Having created a JButton object, we need to place it in the container, but where? An object called a LayoutManager determines the location within the HelloComponent3 container at which to display the JButton. A LayoutManager object embodies a particular scheme for arranging components on the screen and adjusting their sizes. There are several standard layout managers to choose from, and we can, of course, create new ones. In our case, we specify one of the standard managers, a FlowLayout . The net result is that the button is centered at the top of the HelloComponent3 container. Our JFrame has another kind of layout, called BorderLayout. You’ll learn more about layout managers in Chapter 19.

To add the button to the layout, we invoke the add() method that HelloComponent3 inherits from Container, passing the JButton object as a parameter:

    add( theButton );

add() is a method inherited by our class from the Container class. It appends our JButton to the list of components that the HelloComponent3 container manages. Thereafter, HelloComponent3 is responsible for the JButton: it causes the button to be displayed and it determines where in its window the button should be placed.

Subclassing and Subtypes

If you look up the add() method of the Container class, you’ll see that it takes a Component object as an argument. In our example, we’ve given it a JButton object. What’s going on?

As we’ve said, JButton is a subclass of the Component class. Because a subclass is a kind of its superclass and has, at minimum, the same public methods and variables, Java allows us to use an instance of a subclass anywhere we could use an instance of its superclass. JButton is a kind of Component, so any method that expects a Component as an argument will accept a JButton. The converse, however, is not true. A method signature expecting a particular class will not accept its superclass as a parameter.

More Events and Interfaces

Now that we have a JButton, we need some way to communicate with it—that is, to get the events it generates. We could just listen for mouse clicks within the button and act accordingly, but that would require customization, via subclassing of the JButton, and we would be giving up the advantages of using a pre-fab component. Instead, we have the HelloComponent3 object listen for higher-level events, corresponding to button presses. A JButton generates a special kind of event called an ActionEvent when someone clicks on it with the mouse. To receive these events, we have added another method to the HelloComponent3 class:

    public void actionPerformed( ActionEvent e ) {
      if ( e.getSource() == theButton )
        changeColor();
    }

If you followed the previous example, you shouldn’t be surprised to see that HelloComponent3 now declares that it implements the ActionListener interface in addition to MouseMotionListener. ActionListener requires us to implement an actionPerformed() method that is called whenever an ActionEvent occurs. You also shouldn’t be surprised to see that we added a line to the HelloComponent3 constructor, registering itself (this) as a listener for the button’s action events:

    theButton.addActionListener( this );

Note that this time, we’re registering our component as a listener with a different object—the button—whereas previously we were asking for our own events.

The actionPerformed() method takes care of any action events that arise. First, it checks to make sure that the event’s source (the component generating the event) is what we think it should be: theButton. This may seem superfluous; after all, there is only one button. What else could possibly generate an action event? In this application, nothing, but it’s a good idea to check because another application may have many buttons, and you may need to figure out which one has been clicked. Or you may add a second button to this application later, and you don’t want it to break something when you do. To check this, we call the getSource() method of the ActionEvent object, e. We then use the == operator to make sure the event source matches theButton.

Tip

In Java, == is a test for identity, not equality; it is true if the event source and theButton are the same object. The distinction between equality and identity is important. We would consider two String objects to be equal if they have the same characters in the same sequence. However, they might not be the same object. In Chapter 7, we’ll look at the equals() method, which tests for equality.

Once we establish that event e comes from the right button, we call our changeColor() method, and we’re finished.

You may wonder why we don’t have to change mouseDragged() now that we have a JButton in our application. The rationale is that the coordinates of the event are all that matter for this method. We are not particularly concerned if the event falls within an area of the screen occupied by another component. This means you can drag the text right through the JButton: try it and see! In this case, the arrangement of containers means that the button is on top of our component, so the text is dragged beneath it.

Color Commentary

To support HelloJava3’s colorful side, we have added a couple of new variables and two helpful methods. We create and initialize an array of Color objects representing the colors through which we cycle when the button is pressed. We also declare an integer variable that serves as an index into this array, specifying the position of the current color:

    int colorIndex;
    static Color[] someColors = { Color.black, Color.red,
        Color.green, Color.blue, Color.magenta };

A number of things are going on here. First, let’s look at the Color objects we are putting into the array. Instances of the java.awt.Color class represent colors; they are used by all classes in the java.awt package that deal with basic color graphics. Notice that we are referencing variables such as Color.black and Color.red. These look like examples of an object’s instance variables, but Color is not an object, it’s a class. What is the meaning of this? We’ll discuss that next.

Static Members

A class can contain variables and methods that are shared among all instances of the class. These shared members are called static variables and static methods. The most common use of static variables in a class is to hold predefined constants or unchanging objects that all the instances can use.

This approach has two advantages. One advantage is that static values are shared by all instances of the class; the same value can be seen by all instances. More importantly, static members can be accessed even if no instances of the class exist. In this example, we use the static variable Color.red without having to create an instance of the Color class.

An instance of the Color class represents a visible color. For convenience, the Color class contains some static, predefined objects with friendly names such as GREEN, RED, and (the happy color) MAGENTA. The variable GREEN, for example, is a static member in the Color class. The data type of the variable GREEN is Color. Internally, in Java-land, it is initialized like this:

    public final static Color GREEN = new Color(0, 255, 0);

The GREEN variable and the other static members of Color cannot be modified (after they’ve been initialized) so that they are effectively constants and can be optimized as such by the Java VM. The alternative to using these predefined colors is to create a color manually by specifying its red, green, and blue (RGB) components using a Color class constructor.

Arrays

Next, we turn our attention to the array. We have declared a variable called someColors, which is an array of Color objects. In Java, arrays are first-class objects. This means that an array itself is a type of object—one that knows how to hold an indexed list of some other type of object. An array is indexed by integers; when you index an array, the resulting value is an object reference—that is, a reference to the object that is located in the array’s specified slot. Our code uses the colorIndex variable to index someColors. It’s also possible to have an array of simple primitive types, such as floats, rather than objects.

When we declare an array, we can initialize it using the curly brace construct. Specifying a comma-separated list of elements inside curly braces is a convenience that instructs the compiler to create an instance of the array with those elements and assign it to our variable. Alternatively, we could have just declared our someColors variable and, later, allocated an array object for it and assigned individual elements to that array’s slots. See Chapter 5 for a complete discussion of arrays.

Our Color Methods

Now we have an array of Color objects and a variable with which to index the array. Two private methods do the actual work for us. The private modifier on these methods specifies that they can be called only by other methods in the same instance of the class. They cannot be accessed outside the object that contains them. We declare members to be private to hide the detailed inner workings of a class from the outside world. This is called encapsulation and is another tenet of object-oriented design as well as good programming practice. Private methods are created as helper functions for use solely in the class implementation.

The first method, currentColor(), is simply a convenience routine that returns the Color object representing the current text color. It returns the Color object in the someColors array at the index specified by our colorIndex variable:

    synchronized private Color currentColor() {
      return someColors[colorIndex];
    }

We could just as readily have used the expression someColors[colorIndex] everywhere we use currentColor(); however, creating methods to wrap common tasks is another way of shielding ourselves from the details of our class. In an alternative implementation, we might have shuffled off details of all color-related code into a separate class. We could have created a class that takes an array of colors in its constructor and then provides two methods: one to ask for the current color and one to cycle to the next color (just some food for thought).

The second method, changeColor(), is responsible for incrementing the colorIndex variable to point to the next Color in the array. changeColor() is called from our actionPerformed() method whenever the button is pressed:

    synchronized private void changeColor() {
        // Change the index to the next color, awkwardly.
        if ( ++colorIndex == someColors.length )
          colorIndex = 0;
        setForeground( currentColor() ); // Use the new color.
        repaint();
    }

Here we increment colorIndex and compare it to the length of the someColors array. All array objects have a variable called length that specifies the number of elements in the array. If we have reached the end of the array, we wrap around to the beginning by resetting the index to 0. We’ve flagged this with a comment to indicate that we’re doing something fishy here. But we’ll come back to that in a moment. After changing the currently selected color, we do two things. First, we call the component’s setForeground() method, which changes the color used to draw text in our component. Then we call repaint() to cause the component to be redrawn with the new color for the draggable message.

What is the synchronized keyword that appears in front of our currentColor() and changeColor() methods? Synchronization has to do with threads, which we’ll examine in the next section. For now, all you need to know is that the synchronized keyword indicates that these two methods can never be running at the same time. They must always run in a mutually exclusive way.

The reason for this is related to the fishy way we increment our index. Notice that in changeColor(), we increment colorIndex before testing its value. Strictly speaking, this means that for some brief period of time while Java is running through our code, colorIndex can have a value that is past the end of our array. If our currentColor() method happened to run at that same moment, we would see a runtime “array out of bounds” error. Now, it would be easy for us to fix the problem in this case with some simple arithmetic before changing the value, but this simple example is representative of more general synchronization issues that we need to address. We’ll use it to illustrate the use of the synchronized keyword. In the next section, you’ll see that Java makes dealing with these problems relatively easy through language-level synchronization support.



[4] Why isn’t it just called a Button? Button is the name that was used in Java’s original GUI toolkit, AWT. AWT had some significant shortcomings, so it was extended and essentially replaced by Swing in Java 1.2. Since AWT already took the reasonable names, such as Button and MenuBar, and mixing them in code could be confusing, Swing user interface component names start with J, such as JButton and JMenuBar.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.