Extending the AWT

Extending the AWT

For more than thirty years of systems development, the software crisis has posed a problem. Generally, systems development surpasses budgets and time limits and is not reliable. Worse still, it doesn't satisfy users' requirements.

Basically, these problems arise because the systems become more and more complex as they grow. They are always changing, too. It is difficult to make them reliable, to test them and to specify requirements for them.

That's why different techniques or methodologies have arisen to ensure the development of a more efficient software. Object-oriented technology directly attacks each one of these problems and this technique is causing a revolution in the software development.

Java is an object-oriented language which includes a new feature: it's platform independent. If we consider the implications which a multiplatform programming language entails, we have to take into account the user's graphical interfaces. We know that the user's interface is the communication language between man and machine and the success of the system will often depend on the efficiency of this interaction.

When we think of how to design a package that provides all the classes relating to graphical interfaces in a multiplatform language, we are basically faced with two options. The first is to create new graphic components which will have a uniform shape independent of the platform where we are. Each user is accustomed to the standard graphical interface of his platform, so it will take time to teach him the new components and their functionalities. But this is only with programs developed with Java. Thus, it is obvious that this option is not very recommended.

Basically, this is why we are able to understand that each component of theawt (abstract windowing toolkit) has its own image on each platform. Better put, a peer object is the object that is really shown on the screen (peculiar to each platform). The object that we program is an interface to this peer object.

This design decision has its disadvantages. The set of graphical components which this package provides is limited to being the intersection of the sets of graphical components offered in each platform that the package will support.

Outside of this subset (the set of the intersection) of components, there is the Image Button, among others. Now, if the user's requirements demand that our application contain Image Buttons, what can we do to support it? Or better yet, does the awt provide a means of extending it with other components that are not supported in it?

The answer to these questions is that it can be extended basically in three ways:
1. Writing components with native methods (limitation in the Applets)
2. Extending the Canvas class
3. Combining the existing components

The first proposal has the restriction that our component won't be able to run within an Applet. The second proposal is the one that will be discussed in this article. The third one will be discussed in a later article.

Extending the awt, Creating a Subclass of Canvas
The idea of creating a new component implies the creation of a new public class, which can be reused whenever and wherever necessary and can be added inside a Container.

Canvas is a subclass of Component, which automatically means that it can be added inside any Container. In addition, it offers a graphic context where any drawing can be done. Therefore, the idea uses the Canvas class in order to get a graphic space where the component can be drawn.

In order to demonstrate how to extend the Canvas class, we will create an Image Button. The reader will be able to follow the methodology discussed here in order to build his own graphical components according to his needs.

Before beginning to write the code of our Image Button (or our specific component), we must have a clear definition of what its possible states are and how it will look on each one.

We can't forget the relations existing between each state because they will define how the component changes depending on the events it receives. The events which have any effect on the component will be called significant events.

In order to have an organized form of this information, we will create two tables and one graph.

The first table will contain two columns; each row will represent one state. The identifiers will be placed in the first column and in the second one, a drawing of the component's appearance.

The second table will contain as many columns as there are significant events and as many rows as there are different states.

The process of going from one state to another will be called transposition. The i,j box contains the state identifier where the component will go if we are at the i state and receive the significant event j, so it will represent a transposition. The i,j box also will contain the event identifier that will be triggered in case transposition should require this.

Finally, we can present a graph which will complement the information presented in the tables, thus giving us a more general vision of the model we are creating and enabling us to get a better knowledge of the completeness of the design.

To build the graph, we must start by creating a node for each state presented in the model. Then for each one of these nodes, we must create the arcs which will connect one node to the other by a significant event. Each one of these arcs must be identified with the name of the corresponding event and each node must be with the corresponding image when this state is reached.

We are going to have two states for the example discussed in this article: the button in normal conditions and the button at the moment of receiving a mouse down event.

The tables are in Figures 1 and 2 and the corresponding graph in Figure 3.

In order to know how to draw the button with a 3D effect, any button presented in our graphical interfaces can be observed, particularly the button class in the awt. We can now see that the button is just a quadrilateral with the lines on the left and at the top drawn in white and the two others in dark gray.

At this point, we will proceed with a simple exercise. Put your finger on the button in Figure 4. Now, turn the image 180¡. The button appears to be pressed!

This way we see that the second state of the button is represented with the same border lines, only rotated 180¡.

This example gives us only two states; the images of each can be included in the graph. In cases where seven or eight states occur, due to space restrictions the state identifiers can be used instead of their images. But, we recommend using the images wherever and whenever possible.

Now that we have clearly defined how our component will change when different events are received, we can write the code that will generate our component.

It's important to understand that the implementation of this example can be generalized to any other, bearing in mind that some optimizations might occur in each case presented.

First, we will have an attribute called state, which will contain the information of the state we are in. We will now see the different methods of the Canvas class that are pertinent (that we will have to overwrite).

The first of these methods to consider is the paint() method, which is called up every time the component is needed to be drawn. In this method we should draw the component depending on the state we are in.

Another method to be considered is the handleEvent(), which will be summoned when an event occurs. Taking the information from Figure 2, we can easily write the code that will filter the different significant events and make the state transpositions.

Finally, we should overwrite the preferredSize and minimumSize methods. These methods are used by some layout managers to resize the components inside the containers. They should return the desired space size for the component drawing.

Should you forget to overwrite these methods, a zero-sized dimension will be returned. Thus, our components will not be displayed on the screen for layout managers who employ them.

We are going to use an attribute state of type int and we will define two constants to represent each one of the states. The first will be called normalState and the second mouseDownState.

The constructor method receives the image which will be displayed inside the button; this will be kept in the attribute image of type Image. It also sets the initial state.

public ImageButton(Image y)
state = normalState;

For other components, we should receive some other initialization values. Therefore, we should proceed with the same idea.

There can be a method for the drawing of each state. For example, we can have a method that takes care of the drawing of the normalState and another for that of the mouseDown. They should receive a Graphic Context as a parameter, which is the graphic space where the component is to be drawn.

The component will be drawn making calls to the different methods offered in the Graphics class. The reader is recommended to carefully study the corresponding API.

It's important to point out that this article intends to describe a generic methodology which can be optimized in each particular component.

The paint method receives a graphic context as a parameter which represents the component drawing space. In this method, we can view the state we are in and call the corresponding method of drawing.

Writing a method for the drawing of each state is not justified in this case, since there's only a slight difference in the color exchange of the border lines between the two states.

In cases where the difference is considerable, it will be convenient to create different routines for the drawing of each state independently.

Therefore, considering the similarities between the two states, we will use one routine. We will begin this routine by drawing the image with the drawImage method of the Graphics class. Afterwards, we will proceed with the color setting of the left and top lines with the setColor method of Graphics class. Now we will draw the left and top border lines with the drawLine method of the Graphics class. The same procedure will be applied to the other lines.

To select the respective color, we will use the conditional operator "?" with the attribute state as a conditional expression compared with the constant normalState or mouseDown. (We know that if we are not in normalState/mouseDown, we are in mouseDown/normalState).

With regards to managing events, we have to consider each one of thetranspositions and write the code to represent each one of them. For this purpose, we have the information contained in the second table that describes what the transpositions are.

In this case we can use the mouseDown and the mouseUpmethods. On receiving any of these events, we will modify the state value to the new corresponding one. Then we will originate the drawing of the new state calling up the repaint() method. Where justified, we should trigger the corresponding event, which is done by calling the postEvent() method.

Finally, we have to overwrite the preferredSize() and minimumSize() methods, which will indicate to layout managers the dimensions they need to have displayed.

In the case of the Image Button, the size will be determined by the size of the image.

The corresponding codes for these methods are listed in Listing 1.

Now we have created an Image Button, which can be reused in any Applet or stand-alone application and one that is multiplatform.

I hope that the reader has found this article beneficial and will take advantage of it by applying the methodology described here to create his own components.

More Stories By Elie Levy

Elie Levy lectures on Java Basic Programming, Java Advanced Programming and Java Graphical Interfaces for Sun Microsystems events in Caracas, Venezuela. He is an Internal Software Developer for Sun Microsystems in Java, working on a network administration tool (Java NAT) and a CASE tool for OOA and OOD with a translator from the graphical language of classes representation to Java source code and vice versa.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.