Into Java - Part VIII

From EDM2
Jump to: navigation, search
Into Java / Part
I II III IV V VI VII VIII IX X XI XII
XIII IV XV XVI XVII XVIII XIX XX XXI XXII XXIII

By Simon Gronlund

Welcome back to this eighth installment of this Java column. I wouldn't have mentioned how lovely Swedish summers can be since this summer seems to be one of the most rainy summers within living memory.

Still, by some strange coincidence my family seems to hit the few sunny weekends for our excursions. So far strawberries, perch and some mushrooms have found the way down our throats. Let's hope it will continue this way or getting even better.

Last time we finally arrived at the GUI programming area and we will continue today. The result of today's lab will be a small application with three buttons, modelled after one of the Java tutorials at SUN's web pages. It will show us how to add an icon to them, add flyover help bubbles and how to disable and enable buttons.

Today there will be no theory discussion, but a short review on some topics from the past. Next time we will expand our knowledge on some Object Oriented Programming (OOP) concepts, some that we have used but not discussed much. To that we will add a deepened use of the Graphic class.

The Event Model

As stated the previous month, every time we would like to use graphical objects, like buttons, menus or alike, we will treat them as sources or originators of events. Java handles these events by instantiating Event objects reflecting the action that took place. Clicking a button will cause an ActionEvent to be created. And these actors will be built automatically by the Java environment.

Hence we now have both an event source and an event object. But who will listen? The third part of the model is the event listener that will listen for event objects. One way to construct a listener is to use the interface mechanism, using the keyword implements. Using interfaces gives you an opportunity to use a cheat multiple inheritance, but instead of inheritance you make a promise of implementing certain methods yourself.

Another way to use listeners is to use adapter classes and within them override the empty methods of the adapters. Anyway we always have to tell the Java system what object will act as a listener by adding it to the event source. Then the bridge between these two is thrown, and later is trafficked by event objects.

ButtonDemo

ButtonDemo image - Link to original size

A little application like the one to the right (click it for larger size) will be the result of today's lab. It maybe does not look much but still there are some news added to it. Admittedly some of more cosmetic kind, but beneath the surface some useful lines of code are added. As usual we will work ourselves through the code line by line.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
 
public class ButtonDemo extends JFrame implements ActionListener {
    private JButton leftButton;
    private JButton middleButton;
    private JButton rightButton;
 
    private String leftButtonImageName = "right.gif";
    private String middleButtonImageName = "middle.gif";
    private String rightButtonImageName = "left.gif";

The import statement

We will start with the import statements. We have used the keyword import many times but never payed much attention to it. Unlike other programming languages we do not actually import (or #include) anything, but import gives a shorthand to the many classes in the packages we are using, like java.awt, javax.swing and others.

There are three ways to get in touch with the classes of Java:

  1. Refer directly to the class and actual method, i.e.
 javax.swing.JButton butt = new javax.swing.JButton("MyButton");
  1. Import only one class out of a package, i.e.
 import javax.swing.JButton;
...
JButton butt = new JButton("MyButton");
  1. Import a whole package, i.e.
 import javax.swing.*; // every class of the javax.swing package
 JButton butt = new JButton("MyButton");
 JLabel label = new JLabel("MyLabel"); // possible as well

From time to time we refer to a class only once and then it is unnecessary to import that class, i.e. double rand = java.lang.Math.random(); that gives us a random number. But more frequently we use the import mechanism of Java to get to one class or several. What will happen then?

Upon compiling, the import lines instructs the compiler where to look for the classes we refer to and are using. The JDK compiler (javac) and most other, but not all, Java compilers searches several possible directories to find the class we are referring to. Normally the compiler starts to search the JDK environment, more exactly the classes.zip located by the CLASSPATH environment variable of your system. Within that zip-file the compiler tries possible directories that the import statements implies, whether the directories exist or not. Then the compiler continues with every possible path set by the CLASSPATH, like swingall.jar and others. Finally the compiler concatenates the current directory (.) with the import statements and tries to find what it looks for.

The only package we never need to import is java.lang that is always searched, treated as the universal package and the origin of all beings found in the community of Java.

In a future column we will discuss what exactly is a package, until then we may know them as a group of classes, or a collection of classes. The personnel at SUN have neatly structured the many classes in packages reflecting the purpose of the classes, i.e. java.io, java.net, etc. Whenever you start creating your splendid Java tools yourself you may collect them into a directory that will be your package.

Class declaration

Since we will make this class a common application we extend it from JFrame, found in javax.swing. Hence we now have a class that we will design ourselves, but to that it more formally speaking is a javax.swing.JFrame, that is a java.awt.Frame, that is a java.awt.Window, etc.

To that we add the promise of implementing the method(s) of the interface ActionListener. Looking java.awt.event.ActionListener up we will find that only one method was asked for, namely public void actionPerformed(ActionEvent e). So, later we will see to it and implement such a method. If we distrust ourselves we might add the method right away as an empty method.

Variable declarations

Last time we did not declare the two buttons we used as variables. That was possible since we used the ActionCommand feature of event sources, inheriting from javax.swing.AbstractButton that is kind of a superclass of many GUI items. Today we will use another way of determining the event originator in the actionPerformed method. But the main reason why we declare the buttons as variables is that we like to refer to them later on, hence we need a reference to them.

We declare the image file names as variables as well. That will mainly remind us of the good habit of grouping information that we may change later on among the variables. In fact that will not increase the memory usage since only one object is made, but references to it are used. This time we can assume that the image file names will change over time. Furthermore, never hesitate using these long but descriptive variable names. In spite of the abundant typing you will save time from looking up those clever but so-hard-to-remember abbreviations and acronyms people love to use.

The Constructor

   /* Constructor */
   public ButtonDemo() {
       setTitle("ButtonDemo");
       setSize(600, 80);

       Toolkit tk = Toolkit.getDefaultToolkit();

Continuing with the constructor, one that takes no parameter, we give the frame a title and an initial size. How to find the 600 by 80? By trial and error <grin>. Then comes a line that is new to us. What is Toolkit

Class Toolkit is found in java.awt which implies that it is a class handling user interfaces, painting graphics or images. The Java API tells us that "subclasses of Toolkit are used to bind the various components to particular native toolkit implementations. ... The methods defined by Toolkit are the "glue" that joins the platform-independent classes...." The purport of this is that a toolkit acts as a channel, or "glue", to the system-dependent information we would like to query or system-dependent resources we would like to use. Hence we get ourselves the reference tk that will give us access to the system you are running, that may differ from your neighbor's system.

/*
 * Find the middle position on the screen, all screen sizes.
 */
Dimension dim = tk.getScreenSize();
int w = (dim.width - 600) / 2;
int h = (dim.height - 80) / 2;
/* Are there such small screens around? If so... */
if (w < 0 || h < 0) {
   w = 0;
   h = 0;
}
setLocation(w, h);

Nowadays there are several screen sizes and resolutions around, thus it may be insufficient to write code like setLocation(20, 20). You never know where that will be indeed. A better way is to first query the users screen resolution and then compute a good place to put the application, if you do not use some kind of INI file, but then you are not likely reading this Java column I presume <grin>.

Class Dimension simply encapsulates the width and height of a component (in integer precision) in a single object. There is not much to tell about it except that it may only be used together with java.awt.Component and its subclasses. Not to forget that the width and height are public variables and hence need no accessor methods, you query them directly as the code shows.

How to compute the position of a frame to put it at the middle of a screen? As seen we subtract the size of the frame from the dimension of the screen and then halve the result. (If your frame size is not known to you at this moment you may use the method public Dimension getSize() and maybe put these lines at the end of the constructor.) Since we declared w and h as integers the result of the division will always be truncated down to closest integer upon assigning them, that is one pixel at most.

For any possibility, test that w and h will not be negative. Robust and sound code will always do such tests, how weird they may seem there is always a user more weird anyway. Finally, we set the location of the frame with the now safe values.

/*
 * Construct an icon to the frame
 */
       Image img = tk.getImage("chicken.gif");
       setIconImage(img);

       addWindowListener(new WindowAdapter() {
           public void windowClosing(WindowEvent e) {
               System.exit(0);
           }
       });
       getContentPane().add(makeContent(), "Center");
   }

Using the toolkit we will add an icon to the frame. Images are system-dependent and then we have to use the toolkit. Since we have the reference tk we simply call the method getImage with the icon file name as a string parameter. Note that the image that Toolkit can handle has to be of GIF or JPG file format, thus you need tools like PMView to convert *.ICO files to the GIF or JPG file formats. Finally you add the image as an icon to the frame.

The latter part of the constructor is well known to us, we add an adapter as a listener to the frame and override one of its empty methods. But the next line, what does it say?

We know the sense of the former part of the last line. But we have added a call to a method, makeContent(), that seems to be added to the contents pane. Yes, that is correct! Usually we move out as much code as possible from the constructor to make it more readable, and that code lifted out will be placed in a helper method such as makeContent(). This technique is especially useful when there are plenty of panels and GUI components, each of these components may be constructed in a method of its own. Another profit is that it will be a lot more easy to maintain and rebuild the components.

Adding the panel at the "Center" will be discussed next time, when we will look at layout managers. Until then you may try to change the "North" parameter used the former month (FirstButtonFrame) to "Center" or the other cardinal points. As you noticed then the background color did not fill the entire area of the frame. Why? What is the result if you change this parameter?

The helper method

/*
 * A helper method that makes the code clearer. Not this time but
 * when there are lots of panels the constructor code becomes
 * clearer, and it will be much easier to add / change to a
 * certain panel.
 */
private Container makeContent() {
/*
 * Some code to get the button images. This code can easily
 * be concatenated but this clarifies what's going on.
 */
ImageIcon leftButtonIcon; // only the declarations
ImageIcon middleButtonIcon;
ImageIcon rightButtonIcon;

leftButtonIcon = new ImageIcon(leftButtonImageName);
middleButtonIcon = new ImageIcon(middleButtonImageName);
rightButtonIcon = new ImageIcon(rightButtonImageName);

Here comes the helper method then. With the comments it is much self explanatory. To that we notice that this time we did not use the Toolkit to make ourselves images or icons. That is since we use a class found in javax.swing that in fact uses the Toolkit class behind the curtains. If you find it more convenient, and you like to be spared from using the Toolkit class, you may instantiate an ImageIcon and then use the method public Image getImage(), i.e.

 Image img = new IconImage("anImageFileName").getImage();

as both techniques are the same. Beyond that, there is not much to tell else than recall that the image file names were made variables and hence may easily be revised.

/*
 * Construct the JButtons with the icons
 */
leftButton = new JButton("Disable middle button",
                                 leftButtonIcon);

middleButton = new JButton("Middle button",
                                   middleButtonIcon);

rightButton = new JButton("Enable middle button",
                                  rightButtonIcon);
rightButton.setEnabled(false);

Having the button icons pre instantiated we continue with the buttons themselves, as we did last time except for the icons. With this code you will get the left button's icon to the left, as default is, but try the method setHorizontalTextPosition(int textPosition), where AbstractButton.LEFT, CENTER or RIGHT are valid parameters, i.e. leftButton.setHorizontalTextPosition(AbstractButton.LEFT). And why not the accompanying method setVerticalTextPosition(int textPosition) with AbstractButton.TOP, CENTER or BOTTOM as valid parameters.

Finally we disable the right button, as the application in use will show us why. Recall that</font> false is a basic data type, a boolean. It is very common that only one method is used, such as setEnabled, with boolean true or false sent as parameter. The opposite would be two methods, a setEnabled() and a setDisabled(), which is more clumsy.

/*
 * Listeners for left and right buttons.
 */
leftButton.addActionListener(this);
rightButton.addActionListener(this);

/*
 * The convenient flyover help bubbles.
 */
leftButton.setToolTipText("Click this button to disable the"
               + " middle button.");
middleButton.setToolTipText("This middle button does nothing"
               + " when you click it.");
rightButton.setToolTipText("Click this button to enable the"
               + " middle button.");

So far we have three buttons that each may act as event sources, but we have no listener. Ahem, we made that promise to make this class a listener, then we may add this as a listener. So we do. The middle button, though, has the only purpose of acting as kind of a light bulb, thus we do not add a listener to that button.

A new touch is the convenient "flyover help" that will guide new users of this splendid and complex application around.

/*
 * Make a JPanel and add the components to it.
 */
JPanel pane = new JPanel();
pane.add(leftButton);
pane.add(middleButton);
pane.add(rightButton);
pane.setBackground(new Color(255, 0, 0));

return pane;
}

Finally we add the three buttons to a fresh JPanel, given the background color red. You may experiment with different values for Red, Green and Blue that makes up the three figure combination of RGB, any value between 0 and 255 is valid.

Recall that this helper method was supposed to return a Container, a superclass to many kind of graphical components used in a Java application. The signature of this method is private Container makeContent(), hence we have to return the panel we made. Such helper methods are, as said, commonly used and they more or less always follow this style. Imagine this app having several such panels, how lengthy the constructor would become if no helper methods are used, now only one line each container is necessary.

The listener

Because of that promise we made to make this class an ActionListener we have to add the method beneath, that is the only method of the java.awt.event.ActionListener interface. The method will get an event object as parameter and searches it for who the event originator is. This time we use another approach, we try if the reference given by getSource refer to the same object as any of the buttons we made ourselves. Since references always points to objects (and in fact are closely related to pointers, memory addresses) we may compare them directly, as in "is 'address of the event source' the same as 'address of leftButton'?" The answer will decide which action is chosen.

    public void actionPerformed(ActionEvent e) {
        Object eComp = e.getSource();
        if (eComp == leftButton) {
            leftButton.setEnabled(false);
            middleButton.setEnabled(false);
            rightButton.setEnabled(true);
        } else if (eComp == rightButton) {
            leftButton.setEnabled(true);
            middleButton.setEnabled(true);
            rightButton.setEnabled(false);
        }
    }

The driver

Now it is time to start this application and see if we managed to make it without typos. Yes, I am still doing mistakes and misspell a lot of code, but still, it is nice that the compiler tells you now and you will not get that many of the never absent bug reports.

public static void main(String[] args) {
        ButtonDemo frame = new ButtonDemo();
        frame.show();
    }
}

This time we added a few gadgets to our tool box. But foremost we once again went through the event source--event object--action listener model. Once we understand this model it will be an easy task adding to an application's GUI. Helper methods were used and advocated and how the import mechanism works were briefly discussed.

Next time we will use some more items from Swing, discuss some OOP concepts that are essential to know about, have a look at layout managers, how they work and what shortcuts there are and look at the Graphics class. Now I hope that the rain will stop and tomorrow will be a sunny day. See you next month.

The [ButtonDemo.java] and the [javaimages.zip].