Journal Articles
Browse in : |
All
> Journals
> CVu
> 113
(22)
All > Topics > Programming (877) Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Software Development with Java User Interfaces II
Author: Administrator
Date: 03 April 1999 13:15:30 +01:00 or Sat, 03 April 1999 13:15:30 +01:00
Summary:
Body:
A man with only a very short time to live has a choice to eat meat or drink wine. He has to eat right now, right this minute, and whatever he consumes, he knows that it will be his last action. If he drinks the wine first then he misses the chance to savour the succulent tender red meat, but if he devours the meat he will definitely miss the fine white wine. I am going to serve a lot red meat in this article and hopefully you will survive to drink the white wine only because C Vu is a bimonthly!
Java only supports single inheritance it does not multiple inheritance. Every object in Java extends (derives from) the Object class. However the Java language does support an elegant alternative to multiple inheritance called Interfaces. An interface type acts very much like a class type. You can declare variables to be of an interface type. You can pass them to methods too and they can even be a type returned from a method. However the key difference between a Java class and a Java interface is that an interface defines a contract. What does this contract mean? The interface's contract is very much like a big badge which somebody in a superstore wears which says, "Hi, I am your faithful superstore helper, I am a class type X which is capable of doing Y just for you." The interface in our Java superstore promises, in other words, to act out a certain behaviour or a set of behaviours. We say that class X implements a particular service Y. A Java interface is also similar to an abstract class, because it does not contain implementation code just the heading of methods. Here is an example of a Java interface called Flyable.
Interface Flyable { // assume gravity is 9.81g void fly( float drag, float thrust, float lift ); }
An interface is defined with the keyword interface and list of methods with no bodies. The methods of any interface are always public, because other classes would not be able use it if this was otherwise.
In order to use any interface it must be specified as part of the class definition. The clause implements is used after the extends keyword. This is equivalent to saying, "I, class X, promise to acknowledge and uphold the contract, Y" which is exactly another way of writing the above assertion. The next step is to write the bodies of all the methods in the interface definition. An interface implementation is not inheritable, each new subclass requires its own method bodies. Here is an Aeroplane class that implements the Flyable interface.
class Aeroplane implements Flyable { boolean propellerRunning; public void fly( float drag, float thrust, float lift ) { if (propellerRunning) { //...fly } } public boolean isPropellerRunning() { return (propellerRunning); } public void setPropellerRunning( boolean flag ) { propellerRunning = flag; } }
The advantage of Java interfaces are the clear separation of interface and implementation, since code can refer to interface types instead of fixed class names. It therefore lends itself to the generic containers like those in the Collection API for example, and without interfaces much of event handling for AWT component would be more difficult to write.
In my last article, the VideoRecorder1 application displayed correctly, but it was not complete. It did not respond to user input. The AWT defines a number of class events in the package java.awt.event. MouseEvent, MouseMotionEvent, KeyEvent, FocusEvent, and ActionEvent are examples. For the purpose of the Digital Video Recorder example it would be nice to add communications to the Button components. All AWT Components communicate by sending events. In Java vernacular, we developer talk about "firing" events and "receiving" (or "handling") them. All AWT Events are just ordinary Java object classes; no special syntax or constructs are required whatsoever.
An event is fired from a source object, say an AWT Button, to one or more listeners (also known as receivers), obviously our application. (You may be already be familiar with C/C++ callbacks in X/OSFMotif or MFC GUI Programming, please note that the Java AWT approach is much better, because it applies the Publish/Subscribe design pattern directly.) A listener implements particular event handling methods in this way. It can then respond to an event from the source object. However in order for a listener to receive any events, it must first register itself with the source of the event.
When an AWT Button is pressed and depressed it fires a java.awt.event.ActionEvent to all its registered action event listeners. The event is delivered to any object class that implements the java.awt.event.ActionListener interface. The definition of this interface is show below:
interface ActionListener extends java.util.EventListener { pulic void actionPerformed( ActionEvent e ); }
The event is passed to method called actionPerformed() as a parameter. Similar method declaration exists for the other listeners and event types like KeyListener and KeyEvent. The ActionListener is sub-interface of java.util.EventListener, but EventListener is only an empty class definition, and it is used only for type identification. The ActionEvent is derived from the AWTEvent class. The AWTEvent class maintains an important property called source, which is a reference to the object that fired the event. Thus a listener can establish what component object fired the event by calling the getSource() method.
Okay that takes care of destination end of the stick, but what about the source end. The AWT Button has two methods that register and unregister action event listeners. These are:
public void addActionListener( ActionListener listener ); public void removeActionListener( ActionListener listener );
Here is an example that puts all the above together and it should be much clearer now:
public class AReceiver implements ActionListener { Button aButton; public AReceiver() { ... aButton = new Button("Press Me Now!"); aButton.addActionListener(this); ... } public void actionPerformed( ActionEvent e ) { Object src = e.getSource(); if (src == aButton) System.out.println("A button was activated."); } }
Note that the AReceiver class registers itself with the button in the constructor, because it is an ActionListener type. It contracts to implement the behaviour actionPerformed(). In the handler method the reference to the original button is required, so that it possible to test the source of the event.
Going back to our DVR example we simply now have to make the VideoRecorder class become an ActionListener as well. We implement the ActionListener interface, and once that is done write body of the actionPerformed() method . We register our class with all the buttons created in the constructor. After all changes are made the VideoRecorder class will now respond to user input events.
// VideoRecorder2.java import java.awt.*; import java.awt.event.*; public class VideoRecorder2 extends Frame implements ActionListener { // Member variables protected Button bt_eject, bt_stop, bt_rec, bt_rew; protected Button bt_play, bt_ffwd, bt_pause; public VideoRecorder2( String title ) { // pass the title to the superclass frame class super(title); setLayout( new BorderLayout() ); ... // Create VCR buttons bt_eject = new Button("Eject"); ... bt_eject.addActionListener(this); ... } public void actionPerformed( ActionEvent e ) { // Event listener for ActioEvents Object src = e.getSource(); if (src == bt_eject ) System.out.println("Tape ejected!"); else if (src == bt_stop ) System.out.println("Tape stopped"); ... else if (src == bt_play ) System.out.println("Tape playing"); ... } public static void main( String [] args ) { VideoRecorder2 dvr = new VideoRecorder2("ACCU Digital Video Recorder"); dvr.pack(); dvr.show(); }
Notice in the actionPerformed() method of VideoRecorder2 the source object is used in a chained comparison to tell which button originated the action event. AWT Buttons support an alternative method to identify the source object known as action commands, which is simply a fancy name for an ordinary string. The action command associated with an AWT Button can be accessed and modified with:
public void setActionCommand( String cmd ); public String getActionCommand();
The ActionEvent also supports retrieval of the action command string as a property of the action event with the method getActionCommand(). With this information we can write different constructor and receiver methods.
public class VideoRecorder3 extends Frame implements ActionListener { // Member variables protected Button bt_eject, bt_stop, bt_rec, bt_rew; protected Button bt_play, bt_ffwd, bt_pause; public VideoRecorder3( String title ) { // pass the title to the superclass frame class super(title); setLayout( new BorderLayout() ); // Create VCR buttons bt_eject = new Button("Eject"); ... bt_eject.setActionCommand("eject"); bt_eject.addActionListener(this); } public void actionPerformed( ActionEvent e ) { // Event listener for ActioEvents String cmd = e.getActionCommand(); if (cmd.equals("eject") ) System.out.println("Tape ejected!"); else if (cmd.equals("stop") ) System.out.println("Tape stopped"); ... else if (cmd.equals("play" ) ) System.out.println("Tape playing"); ... } }
Why should anyone use action commands? Because it further decouples the Buttons from the listener code.
We could throw away the class members bt_eject, bt_stop, and bt_play etc. or move them all inside the main constructor as normal automatic variables.
Did you understand all of that? Suppose later on after developing our Digital Video Recorder for sometime, we decided to add a Menubar, and a drop down Menu with MenuItems for stopping and playing. (A MenuItem is similar to a Button component in behaviour and attributes.) We would have two ways for the DVR to play a video: either pressing & depressing the DVR play button directly or navigating the menubar and its drop down menus. There would be more than one event source that produce a play ActionEvent. Consequently we would need to store a reference to the menu item variable in our source code somewhere. But where and how should we store this variable reference? The logic of the receiver method would become more complicated as a result. Of course this becomes tedious if we decide, still later on, to have more than two play action event sources in our code. Comparing object references and maintaining them would be time consuming and inefficient, whereas the equivalent action command string can be stored in both the menu items and the DVR buttons. In other words there is one event handler in the source code receiving events with shared messages from multiple sources. Most of the time we are not concerned what the source is anyway, we are only concerned with the context of message itself which is simply says, "Play it again, Sam".
void createMenu() { Menu menu = new Menu("VCR Controls"); MenuItem mi_play = new MenuItem("Play"); mi_play.addActionListener( /* VCR Object */ ); mi_play.addActionCommand("play"); menu.add(mi_play); }
Last but not least the source code is available from my home page on Demon Internet http://www.xenonsoft.demon.co.uk/accu.html where you can also retrieve a version of the DVR that uses a custom AWT component called the ImageButton. The standard AWT does not have such a component that displays a GIF/JPEG image and a text label together. The component makes use of the package java.awt.image and the classes java.awt.Image, java.awt.Canvas , and java.awt.MediaTracker.
Drink the wine quickly now, and have fun with AWT and Java.
Notes:
More fields may be available via dynamicdata ..