|
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:
- 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.
- Netscape and Sun promised easy migration path's for both JFC
and AWT users.
- The AWT needs many of the IFC's features, so this is a good
thing.
- 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.
Sample code 1.
Things to notice
- 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.
- We extend the Frame class.
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.
- 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:
- A Component had to filter all of the events within it. Even
those events which are not interesting to it.
- 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.
- 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.
- 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.
- 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.
- The show method of the Window class too, is needed since the
the frame is by default invisible.
- 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:
- It can hover, or dock into place.
- It can move bettwean dockable and hover mode but using a button (I name
it Detach/Dock).
- 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:
- It must be in a filename made up from its class name.
- It must be public.
- It must be in a sub directory matching the package name.
- 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:
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
- 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.
- We add all the buttons to a Vector to make it simple to
process events in a later stage.
- We handle the events in the JDK 1.1 style by dispatching events
to the listening classes.
Goals to improve (in chronological order):
- Support for image buttons.
- Support for hovering help.
- Automatic support for docking and detaching.
- 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.
Sample code 3.
Things to notice
- 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.
- 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.
- 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.
- The Tooltip is not a Component by the full definition in the
Java language, since it does not occupy a space.
- 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.
- The tooltip class is not very generic.
- 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.
Sample code 4.
Things to notice:
- 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:
{
}
}
- 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.
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:
- 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.
- 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.
- 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.
- 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:
Sample code 6.
Things to notice:
- 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.
- 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.
|