Grinding Java - Enhancing the AWT

From EDM2
Jump to: navigation, search

Written by Shai Almog

Introduction

This month we will be digging into the AWT. Those of you who keep up with Java related news have probably read the following announcement:

  • Netscape and Sun jointly announced an exciting new initiative to deliver Java Foundation Classes (JFC) later this year.

What does the new JFC mean to us?

Both Netscape and Sun are not 100% clear about that, and there are some problems with this merge but the following points can be concluded:

  1. AWT is the de facto standard! AWT will be here for a long time, there will probably be some incompatibilities, but no total drop of support.
  2. Netscape and Sun promised easy migration path's for both JFC and AWT users.
  3. The AWT needs many of the IFC's features, so this is a good thing.
  4. We don't have much choice, until we have the actual JFC docs in our hand's we have to use AWT, that is what Sun recommends so far.

VisualAge for Java has FINALLY reached beta. I have been using the win32 beta for a short while. Why can't they make a slick product? I really didn't like the amount of intrusion and the slow speed, not to mention the save/compile feature, which is nice at first but really gets on your nerves! I make very little use of the product but when there will be less bugs (and an OS/2 version) I might start using it.

And now for this month's feature:

In 91 I was convinced that the GUI would take over the world, so naturally I got a copy of windows 3.0 and a book for programming windows 3.0 in C and got to work. I was amazed at how much I hated working and programming in the GUI environment and attributed this to my command line habits, but finally I got tired of both and removed windows 3.0 from my disk. It took some time for me to try OS/2 but I did a year later and I got totally hooked. I immediately decided to turn to GUI programming under OS/2 but found it to be no less a pain under OS/2 than under windows 3.0.

Then RAD tools and class library's started coming out. Was it these tools that made the GUI successful or was it the difficult GUI programming that made these tools necessary? I don't really care, now that OO programming is a way of life I would never develop a high level system without an OO tool.

This brings me to this month's topic. The Abstract Windowing Toolkit (AWT). Some would rather fully teach the lower parts and basics of Java before reaching the AWT (there is a lot I didn't even touch last time) but I believe the best way to learn is by doing real programming and not some "Hello world" joke toys. As we go along I will slowly reveal the inner workings of Java to you.

There has been much criticism about the AWT and many claim that it is the single worst part of the Java language. JDK 1.1 improved many of the faulty concepts of the AWT and thus broke some compatibility with JDK 1.02.

AWT is built around some very powerful concepts which sometimes are its downfall too, AWT does everything to be as portable as possible, but as you will gain experience you will find out how hard portable is even in Java.

Hello AWT

An example is the best way to start with a class library that is why in this article I will focus very heavily on examples and try to explain them as well as possible. This small application will create a small window with an exit button and a label that says "hello AWT". Remember this code is for JDK 1.1 and will not work with JDK 1.0 due to the changes to the event engine.

grinding1.java Sample code 1.

Things to notice

  1. I have made use of the import statement before, but I spent little time explaining its full meaning. Imports are nothing like C/C++'s header files. They are similar to Pascal's units and somewhat like DLLs. When you request an import that means that your class will make use of the class requested, however the compiler will not link them together. You must minimize the use of imports when writing applets to prevent unneeded class code being downloaded. The compiler cannot perform "optimizations" on imports since Java is a dynamic and the compiler can't tell at compile time which classes will be used and which ones will not be used.
  2. We extend the Frame class.

Diagram of AWT relationships As you can see from the diagram Frame is actually a Window which is a Container which is a Component. The concept: If we want to create a new UI object we simply inherit Component and build around it, if we want our UI part to contain other Components within it we inherit Container which is a Component too. I will explain the major types of Components and Containers below.

  1. We implement the ActionListener interface. This interface has one method: actionPerformed(ActionEvent E). When we wish for a class to handle events (such as buttons pressed) we must implement this interface. This calls for a full explanation of JDK 1.1's event mechanism, which is radically different (and better) from the JDK 1.0 event module. In JDK 1.0x whenever an event occurred the action method of the Component in which the event occurred was invoked with the appropriate parameter for the event. This event module presented several problems:
    1. A Component had to filter all of the events within it. Even those events which are not interesting to it.
    2. Only components could filter their event's. This caused excessive use of inheritance and lot's of event specific code in component s rather than separating code to smaller parts.
    3. On windows platforms (and OS/2) the event module is quite similar to the old module, but under Motif the event module is quite similar to the new module. This caused the old module to behave slower than it should on these platforms. The new event module gives Motif an extra performance boost while not slowing down the other platforms.

The new JDK presents a listener paradigm. When a component may generate events that are of interest to the programer, the programer calls the addActionListener() method with a parameter of a class that implements the ActionListener interface. When ever an event occurs the Component class calls the ActionListener.actionPerformed(ActionEvent) method. As you will see in the example above we used the line:

exitButton.addActionListner(this);

to register interest in the events generated by the exitButton Component, the parameter "this" given to the method is actually an instance of the class we are in (this is identical to its C++ meaning). To clarify when we wish to tell a method or a class who we are (what class is calling that method) we give that class the this object which is a pointer to ourselves. i.e. this.exitButton is the same as exitButton. I did not separate the event mechanism and the UI in this example to keep it small but I will do so later on because I think it's good programing practise, so that the same class handles the Frame and the events related to the button in this example.

  1. The setLayout method of the class Container is a major part of the AWT concept. The AWT extends the Java concept of portability by trying its best to avoid any actual sizes or locations of any components. In a nutshell, in AWT if you want to place a Component in a Container you use the method add of the Container class and let AWT decide what to do, that way you will not have to build logic to support different resolutions. The layout of a Container determines where the Component will be placed relative to the other Components in the container, i.e. the border layout can except up to 5 Components each component will be aligned to the appropriate border of the Container when added.
       add("South",exitButton); // Adds the exit button to the lower part
                                // of the screen

In BorderLayout you can add components to 5 different locations North, South, East, West, Center. I will make a full review of all the layouts available to Java.

  1. The pack method of the Window class is a trick to avoid specifying a size for the window . Try removing it and you will find that the window is created as a very small frame with no canvas, the pack method groups all the components together and resizes the window so they will all fit nicely.
  2. The show method of the Window class too, is needed since the the frame is by default invisible.
  3. I hope that you noticed in the diagram above that class Button inherits from class Component. It's not shown but class Label inherits from TextComponent which inherits from Component too. This is a very important step in understanding AWT. Everything you see is either a Component or a Container (which is a Component too).

I suggest running javap java.AWT.Component to get a better feel to what is a Component and what is expected of it. I'll go over the major methods soon.

java.AWT.Component

Warning: Don't use the action(Event, Object) method as it is no longer supported. If you look at the AWT JavaDoc API you will find the word deprecated next to many methods. That means they were a part of JDK 1.02 but will no longer be supported.

I used the JavaDoc java API documentation to build these next two sections. I only wrote down the methods which are important at this stage to under standing the role of this class.

These methods are sorted in the following categories that all components must support.

Event Handling

addComponentListener(ComponentListener)
Adds the specified component listener to receive component events from this component.
addFocusListener(FocusListener)
Adds the specified focus listener to receive focus events from this component.
addKeyListener(KeyListener)
Adds the specified key listener to receive key events from this component.
addMouseListener(MouseListener)
Adds the specified mouse listener to receive mouse events from this component.
addMouseMotionListener(MouseMotionListener)
Adds the specified mouse motion listener to receive mouse motion events from this component.
disableEvents(long)
Disables the events defined by the specified event mask parameter from being delivered to this component.
dispatchEvent(AWTEvent)
Dispatches an event to this component or one of its subcomponents.
removeComponentListener(ComponentListener)
Removes the specified listener so it no longer receives component events from this component.

Adding/modifying visual features and properties to a component

add(PopupMenu)
Adds the specified popup menu to the component.
setFont(Font)
Sets the font of the component.
setForeground(Color)
Sets the foreground color.
setEnabled(boolean)
Enables a component.

Checking the visual state of the Component and its size/location

isEnabled()
Checks if this Component is enabled
getFont()
Gets the font of the component.
isShowing()
Checks if this Component is showing on screen.
isVisible()
Checks if this Component is visible.
getLocation()
Returns the current location of this component.
getLocationOnScreen()
Returns the current location of this component in the screen's coordinate space.

Checking the desired/possible state of the Component

getMaximumSize()
Returns the maximum size of th is component.
getMinimumSize()
Returns the mininimum size of this component

Checking none visual statistics of the Component

getName()
Gets the name of the component.
getParent()
Gets the parent of the component.

Other methods

These 2 methods I could not fit into any of the above categories. Paint is called whenever the component needs to be redrawn. If you make changes to a component you should call repaint and it will call paint for you.

paint(Graphics)
Paints the component.
repaint()
Repaints the component.

To summarize this information: a Component is responsible for event dispatching (even though it does not have to handle them any more), it has mostly display information in it and a little general information. A component is the lowest common denominator of buttons, labels and even frames and panels.

java.AWT.Container

Don't forget that a Container is a component itself and all of the methods described above are valid to it too.

Layout and additions of components

add(Component)
Adds the specified component to this container.
add(Component, int)
Adds the specified component to this container at the given position in the container's component list.
add(Component, Object)
Adds the specified component to this container at the specified index.
add(Component, Object, int)
Adds the specified component to this container with the specified constraints at the specified index.
add(String, Component)
Adds the specified component to this container.
doLayout()
Does a layout on this Container.

Event Handling

addContainerListener(ContainerListener)
Adds the specified container listener to receive container events from this container.

Handling the inner Components and getting information about them

getComponentAt(Point )
Locates the component that contains the specified point.
getComponentCount()
Returns the number of components in this panel.
getComponents()
Gets all the components in this container.
getLayout()
Gets the layout manager for this container.
remove(Component)
Removes the specified component from this container.
setLayout(LayoutManager)
Sets the layout manager for this container.

As we can see by the methods in a Container, a Container extends a Component mostly in one direction. It has some extra functions but most of its functionality centers around maintaining the list of Components and it lets the Component class handle all of the event and display methods.

Finally making something useful: The dockable toolbar class.

I remember the first team OS/2 meeting where we actually discussed Java. Being one of the three people there who actually tried Java I tried to explain its advantages and disadvantages. I claimed that Java is based on a subset API and not a superset API, and thus it will be a problem to make something like a dockable toolbar in Java without reinventing the wheel, in a portable way, since dockable toolbars are supported in windows but are lacking in many other platforms. I found out as I delved deeper into Java how easy "reinventing the wheel" in Java is. Building a dockable toolbar was not so easy, but that was because I insisted on every single feature (such as tooltips), the basic toolbar class is relatively simple.

The dockable toolbar I developed in Java is not so slick as some of the windows ones its functionality is:

  1. It can hover, or dock into place.
  2. It can move bettwean dockable and hover mode but using a button (I name it Detach/Dock).
  3. It uses the standard Button class.

before we get into the actual code writing there are some things I wish to explain:

Packages -- You have already been using Java packages. java.AWT is a package that is a standard part of Java. You can write your own packages and i highly recommend this since it makes code reuse much easier. To be a part of a package a class must comply to these rules:

  1. It must be in a filename made up from its class name.
  2. It must be public.
  3. It must be in a sub directory matching the package name.
  4. The first none white space line must be package PackageName; i.e.
package MyPackage; // first line!!
import java.AWT.*; // goes here

To import your package you can use the ususal import command just as you would import a standard Java package.

import MyPackage.*;

The annoying feature of Java which requires you to place the package into the a sub directory with that name becomes, very annoying once you have to use tools from your package within your package. This problem is solved by compiling from the root with full path to your class:

[c:\Toolbar\]javac Toolbar.java

might not always work so you must use:

[c:\]javac c:\Toolbar\Toolbar.java

Panel - A Panel (as you noticed from the diagram above) is a container too. Its purpose is too group together Components. Why did we choose to implement the toolbar as a panel and not as a frame? So it can dock of course.

Window - In the diagram above the Window class is the parent of the Frame and the Dialogue. The Window class will be of use to us when we detach the Toolbar. In my original design of this class (on JDK 1.02) you could create a Frame and add it to a Container and then it would hover on top. This does not work in JDK 1.1 where you have to create a Window class instance and give it the Frame which owns it, as a parameter.

And now the code:

grinding2.java - Sample code 2.

This is the most preliminary version of the dockable toolbar. It cannot be dragged from docking mode, it does not support image buttons or hovering help.

Things to notice

  1. We create an instance of class Window with the Frame as parameter so that it will hover on top of it and will not be masked by it.
  2. We add all the buttons to a Vector to make it simple to process events in a later stage.
  3. We handle the events in the JDK 1.1 style by dispatching events to the listening classes.

Goals to improve (in chronological order):

  1. Support for image buttons.
  2. Support for hovering help.
  3. Automatic support for docking and detaching.
  4. An ability to drag the toolbar. A problem with the window class is that unlike the Frame class it cannot be dragged.

By now you must have noticed that we only made packages yet I wrote no code which actually uses them:

import GUITools.*;
import java.AWT.*;

public class BuildToolbar extends
Frame implements ActionListener
{
     public static void main(String argv[])
     {
          new BuildToolbar();
     }

     public BuildToolbar()
     {
          setLayout(new BorderLayout());
          T = new DockableToolbar (this);
          T.addActionListner(this);
          T.addButton("Detach");
     }

     public void actionPerformed(ActionEvent E)
     {
          if(E.getSource() instanceof Button)
            if(E.getActionCommand().equals("0")) // Button number 0
                   T.detachToolbar();
     }

     DockableToolbar T;
}

An important thing to notice is the action listener and the creation of the button. We don't write code that uses the actual button class. I personally like to program this way because the underlying code is much easier to change.

Tooltips

To add Tooltip support to the Toolbar we'll create a class to implement the tooltips.

grinding3.java - Sample code 3.

Things to notice

  1. This class extends Canvas, but I would rather it extended Component, the reason I extended Canvas was so it could run under VisualAge for Java.
  2. Drawing the Tooltip is a lot more complicated than it sounds. This is due to the structure of AWT which does not allow Components to overlap. Each Component has a Graphics class which allows us to draw on it, and there is a feature of the graphics class which automatically clips the drawing, i.e. if I have a Panel with several buttons on it and I get a Graphics Component of the panel, I can draw anything on the Panel, but everything that would be in the boundary of one of the buttons will not be shown (it will be clipped). The workaround I found for this was to check the Container's Components and draw on them too.
  3. The Tooltip has to be recreated every time the mouse moves. This is not the most efficient behavior but it works, maybe some day I will change this behavior.
  4. The Tooltip is not a Component by the full definition in the Java language, since it does not occupy a space.
  5. While we might have been able to solve this in several different ways, this seemed to be the best method at the time. If any of you know of a better way I'd be happy to hear from you.
  6. The tooltip class is not very generic.
  7. This is not in the code, but when I tested the tooltips I found out that the dispose() method erases the buttons rather than refreshing them. The solution was to call the setLabel method which repainted the Button correctly.

Hints

As you should have noticed, the tooltips were not generic and had many flaws. That is why I have built a hints class which encapsulates the Tooltip functionality and makes it totaly automatic to create a Hint. BTW in a future release of the JDK there will be tooltip support built into the AWT/JFC.

grinding4.java - Sample code 4.

Things to notice:

  1. The switch statement. I did not mention the switch statment before due to lack of space and I feel that the switch statement is commonly abused. The syntax for the switch command is quite simple:
switch(Variable)
  {
      case value1:
      {
      }
      case value2:
      {
      }
      ....
      case valuen:
      {
      }
  }
  1. We listen to events that are fired by another Component. This is necessary to perform the behavior of the Hints class and one of the biggest improvments to JDK 1.1.

Image buttons

If you will look through the Java developers sites on the net you will find a large collection of image button implementation's. I have yet to find one that is not derived from Canvas.

I implemented my ImageButton class as an extention of the standard AWT Button class. Why? I feel it's more "correct" in the OO sense, and it does give me full plug compatibility with all the classes that accept buttons as parameters. This is all debatable, deriving an image button from Canvas could even be considered easier, but take the following scene: I have ImageButtons in my application, one of my Mac users wants to use a text button since the ImageButton has a Wintel look and feel. If I use the Canvas class, this can pose a problem, but with my ImageButton class both button and ImageButton are one and the same.

Image handling in Java:

In Java images are loaded using the Producer - Consumer - Observer concept. This concept is based on separating the image loading from the image displaying:

  • A producer loads the image and starts supplying information while loading.
  • An observer notifies the consumer of any changes in the producer.
  • A consumer displays the image, there may be several consumers for one producer.

This concept is very good when your loading an image off the net and you want it partially displayed, but it's very annoying when you are working on a local image.

grinding5.java - Sample code 5.

Wow, 432 lines just to display a !&^*%#@ image button! Believe me when I say that I thought this will be simpler, when I set out to do this. The nice thing about this class though, is it's generic. We can now use the ImageButton class with the Toolbar class without modifying anything there, however the Tooltips were not so well designed so we will have to modify large parts of the Toolbar class to support them. At some later stage I hope to include source for a better Tooltip class that will derive from Tooltip and make a more generic version.

Things to notice:

  1. We have to use waitForImage, since the image handling in Java was designed for applets. This image button was not designed to be used on applets.
  2. We use mouse events to monitor the button usage rather than action events. The action events supply one event for button pressing which is not enough. Notice how we call addMouseListener There is no current support for keyboard shortcuts to image buttons, this is easy to implement but I feel this class is big enough.
  3. I used the /** style of comments. This is a style that is very common in Java since the JavaDoc tool makes use of it to automatically generate documentation to Java source code. I am still in the process of getting used to this comment style.
  4. We use the class Toolkit to get the Image from disk. This class is a very annoying (in my humble opinion) part of Java; everything which did not fit in another class was put there. It's a hiding place to many abstract classes with system specific implementations.

The final version of the Toolbar!

Well there is one thing left to add to the Toolbar:

  • Drag and drop support.

grinding6.java - Sample code 6.

Things to notice:

  1. We implement the entire window drag and drop. It gets complicated since when the mouse dragged method is called the points are relative to the Component. That is why we have to compute the relative drag every time.
  2. Tooltips and ImageButtons are fully supported by this Toolbar since they are unrelated. The toolbar doesn't need to know of their existence, that is generic programming.

Well we covered lots of ground with this article, I will add more AWT extensions as we go along (like sliders and message boxes) but now we need to get back to basics. Next time we will go back and study the java.lang.Class and java.lang.Object. These classes are the backbone of Java. After that we will actually start developing useful applications!

The code for the ImageButton and the Tooltip was tested only under windows 95 in JDK 1.1.1. It relies on behavior of the JDK which might vary between implementations. I wanted to test the code under OS/2 and linux but since JDK 1.1 alpha for OS/2 does not seem to work on my system I could not test this code to the full extent.

Why do I publish untested code? There are three answers:

  • The code is generic, so it'll be quit easy to modify to work correctly.
  • Sun will publish soon the JDK 1.2 API which will include both image buttons and tooltips (among other things) so this code will become obsolete soon.
  • It's educational code and does not require the full robustness of distribution code.