Graphical User Interfaces

Introduction

Modern computer systems interact with the user with a mixture of text, images and actions. Words are still an important interaction medium, but various visual and pointing devices supplement and ease the interaction. Windowing systems and Graphical User Interfaces present many new challenges for the programmer, among them the most prominent are:

We begin by discussing the creation of the visuals. Next we add the issues of "events" caused by mouse actions, there dispatch and response, and at the same time deal lightly with the issue of multithreading. We also discuss the Model-View-Controller pattern (MVC).

Example Programs

Java and Swing

Window systems and GUI's (Graphical User Interfaces) are among the largest, most complicated software subsystems in modern computers. They are not, in my opinion, well understood. What this means is that there is not always precise language to discuss and rationally compare different GUI implementations and their peculiarities. Also, the concepts remain unsettled, so there is constant competition between design solutions, and constant change with which to contend.

Java is not immune to these difficulties, and is in fact most ambitiously exposed to them as it tries to create a cross-platform windowing system. I believe that this is the reason that Java's windowing system has undergone such wild reconsideration: originally released with the AWT (the Abstract Windowing Toolkit) it was re-released using an updated event model and then with Swing, the new and preferred windowing API. Further problems required a second re-release in the form of Swing and the Java II platform, also known as Java 1.3, which we shall discuss.

Being cross-platform, Java must connect to the true windowing system of the host platform and interpret its internal models and data structures into that world. This is done using "peers": for instance, for each Java conceptualized window there is a native window on the workstation, and the two do their best to act and remain consistent. It doesn't always work, and even when it does, it doesn't always work as expected.

Graphics programming - the Graphics Object

We will begin with a simple program that draws a flag. The Java GUI system, in collusion with the native windowing system, will present the user with an instance of an object of type java.awt.Graphics (or the new improved java.awt.Graphics2D). This object magically knows how to perform various drawing operations on the screen area that it controls. These operations are all exposed as methods of the Graphics object. The methods of the Graphics object are all possible drawing actions.

The MyView class defined in FirstSwingTest illustrates the use of the Graphics class to create solid colored rectangles. The method fillRect fills a rectangular shape in the current color. The Graphics object keeps track of the current color, and programmer can change the current color using the method setColor. A color is itself an object, and at first acquaintance, you should be satisfied simply to know that there are a supply of ready-made Color objects, Color.red, Color.blue, Color.black, and so on, available for your use.

In the Java windowing system, coordinates are integers, with the first coordinate, the X, increasing from zero towards the right, and the second coordinate, the Y, increasing from zero towards the bottom. The Graphics object controls a certain square of real-estate on the screen, or is perhaps iconified or hidden. The coordinates are respect to the controlled real-estate, not with respect to the true upper left hand corner of the computer monitor. Sizes are also integers, using the same units as positions.

You cannot take or create a Graphics object - it is given to you by the windowing system. It is given to you at the proper time as the argument to a paintComponent method which you must write and locate appropriately in your code. You never call the paintComponent routine directly, it is called automagically by the windowing system. Indeed, since you cannot have a Graphics object except for that given by the windowing system, it is not possible to properly call the paintComponent routine yourself.

This paintComponent routine should, in general, redraw (or repaint) the entire area that it controls. The windowing system calls paintComponent in instances such as when a window is first displayed, when its size is changed, or it is returned to visible after being hidden either partially or fully by another window, or after being restored from an iconized form. It may seem wrong to repaint the entire window when perhaps only a small portion has been hidden, or when only a small area of the visual is being updated, and there are provisions for doing this. But that is more complicated, and most windowing systems have settled on this simpler way of doing things.

As an instance of multithreading, the windowing system will run the paintComponent routine concurrently with whatever else your program is doing at the time. Typically, your program will be doing nothing. GUI programs tend to be "event driven", that is, after running initially to setup the interface and define data structures to guide the repaint, they seem to terminate, and run entirely in response to the window system's calls to paintComponent or to analagous routines similarly called in response to a user's mouse action.

Graphics programming - the Component Object

To have your paintComponent method called by the windowing system you must:

In general, the appropriate class to extend is not Component, but some predefined AWT or Swing class which itself extends Component. FirstSwingTest illustrates the use of JPanel and JFrame. SecondSwingTest also uses a JButton.

The most common class to extend is javax.swing.JPanel. This is a general-purpose Component for drawing on the screen directly, or for organizing other components within its boundaries. Some predefined extensions of Component are ready to use without further extension. A javax.swing.JButton, for instance, is a predefined component which correctly draws a button in either the depressed or released state. When the button is visible with the mouse above the rectangle where the button is drawn, and the mouse button depressed, the windowing system will inform the JButton of its depressed state and then will issue a paintComponent to refresh the button's visual. When the button is released, the window system will update the JButton state to released and again issue a redraw against the JButton by calling its paintComponent method.

A component can be understood, then, as a rectangular area of the screen on which to draw. A container is a type of component in which other components can be located. The windowing system becomes aware of a component, so that it will call its paintComponent method, when it is added to a container which is itself known to the windowing system. At the top-level of this containment hierarchy, there is a special container capable of registering itself directly with the windowing system. Our examples illustrate the top-level container javax.swing.JFrame.

Most of the time, components are added to containers by using the container's add method. The contructor of the MyView class adds the MyButtonPanel and the MyPicturePanel using the add method inherited from JPanel. JFrame, however, is a bit different, since it has several predefined internal components. The internal component to add to is the Content Pane, and is extracted from the JFrame using the getContentPane method. This is illustrated in the main methods of both FirstSwingTest and SecondSwingTest .

   Object (java.lang) 
      |
      +-- Component (java.awt) 
             |
             +-- Container (java.awt) 
                    |
                    +- JComponent  (javax.swing)
                    |      |
                    |      +-- JPanel  (javax.swing)
                    |      |
                    |      +-- AbstractButton (javax.swing)
                    |             |      
                    |             +-- JButton (javax.swing)
                    |      
                    +-- Window (java.awt)
                           |
                           +-- Frame (java.awt)
                                  |      
                                  +-- JFrame (javax.swing)


      Class diagram for JPanel, JButton and JFrame.

Graphics programming - Layout Managers

A Container is an object in which Components or other Containers are sized and positioned. As seen in the class diagram for JPanel, a JPanel is both a Component, having a paintComponent method for visuals, and a Container, having an add method to accept and enclose other JPanels. Although sizing and placement can be done manually, it is usually left to a software mechanism called a layout manager. Layout mangers place and size components according to different rules, depending on the layout manager, and allow a window to be resized by the user and the constituent components to be resized proportionally.

My favorite layout mangers are the FlowLayout, the BorderLayout and the GridLayout.

Every container has a default layout manager installed by the constructor of the container. The default for the JFrame is a BorderLayout, the default for a JPanel is the FlowLayout. If this is not the layout manager desired, the setLayout method of the container can install a different manger. This is done in the MyView class to install a BorderLayout in place of the default FlowLayout.

In order to get the desired layout effect, it is often necessary to nest containers, forming a containment hierarchy. The JFrame created in SecondSwingTest's main method is the top level component, it is the window itself. An object of class MyView, an extension of a JPanel, is added to the center region of the JFrame. A new BorderLayout is installed in MyView, a MyButtonPanel is added in the north and a MyPicturePanel is added in the center. The MyButtonPanel is used as a container for a JButton. The default FlowLayout is fine for centering this one button. The MyPicturePanel is used as a drawing surface the flag. Its paintComponent method is overridden to do this.

   JPanel
      |
      +--(center)-- MyView
                      |
                      +--(center)-- MyPicturePanel
                      |
                      +--(north)--- MyButtonPanel
                                        |
                                        + --(flow)-- JButton

   Containment hierarchy for SecondSwing GUI

Model-View-Controller

My hope is that MVC will structure all this information; not just be an added thing to think about, but ease thinking about all this by preceeding it w/ a framework, which focuses attention on the reason to be of all this machinery. after all MVC exists - things need to be described in order to be viewed (the Model), a good bit of code needs to be written to do the visualization (the View), and there are changes to be effected on the thing to be viewed (the Controller). Breaking things down this way does not add complexity, it structures the complexity which is inherent in the desired outcome.

Events

The method by which GUI's handle user input is very different from how it is handled in text based programs. In text based programs the code drives the data. If input is to be gotten, their is a statement in the program that requests the input, and the program blocks until the input is given. The input is returned to the program in some form, as the contents of some variable, and the program continues with the next line of code, presumably to process and use the input.

GUI's are event driven: the data drives the program. The window system is responsible for listening to user events in the background, while the application program continues with whatever other work it has to do. When the window system receives input it informs the application program by causing an event. The application program has registered with the windowing system a method that the windowing system will call when an event occurs, and the windowing system passes to this method a object with represents the event, an instance of a class extending java.awt.AWTEvent. [note: java.util.EventObject - java.awt.WTEvent - java.awt.event.ActionEvent. [note: it is a good idea to be explicit about class hierarchies, as I am doing here, go back and do this elsewhere.]

A button press, for instance, causes a java.awt.event.ActionEvent object to be instantiated and passed as an argument to the method actionPerformed in any registered listener for that event from that button. The Java type system insures that a registered listener has this method by requireing that all listeners implement the interface java.awt.event.ActionListener. To register for an event with this button, the listening object must have a reference to the button, and call the addActionListener method of the button, passing a reference to itself as the argument.

In the example program secondswing, this is done in MyButtonPanel after the JButton is instantiated. The listener for events from this button is the controller MyController. It is for this reason that a reference to MyController was passed to the constuctor of MyView, and MyView, in turn, passed this reference to the constructor of MyButtonPanel.

As a practical matter: consider that the View needs a reference to the Controller, for the View will be instantiating things such as buttons which the Controller does not have references for, and does not need references for. It only wants to receive events from the button. Meanwhile, in secondswing, the Controller needs a reference to the View, since pushing the button will update within the Controller data which the button needs in order to produce the View. Specially, after updating this data, the Controller needs to signal the View to repaint, and it can only do this if it has a reference to the View. This circular dependence is curious if one considers that either the Controller or the View must be instantiated first, and the one that is instantiated first cannot have a reference to the other, since it doesn't exist yet. This connumdrum is resolved by calling the setView method of the Controller after both have been instantiated [see code MyController and SecondSwingTest]

In summary, graphical widgets accepting user input, such as buttons, communicate the actions by means of events which are sent to listeners. Listeners register interest in getting events by calling the appropriate method on the object sending the events, passing itself as the argument, and implementing the appropriate interface to so that it can receive the events. Events will be received by a call back, a call to the method in the receiving object, given an event object as the argument to the method. The receiving object implements the call back and uses the argument to identify and process the event.

JButtons cause ActionEvents, and the method a listener implements is performActionEvent, so that it implements the ActionListener interface; it registers with the JButton using addActionListener. Other widgets might use slightly different classes. javax.swing.JCheckbox, the widge implementing a checkbox, uses the same classes as a JButton, because both are extension of javax.swing.JAbstractButton. javax.swing.JScrollBar, however, uses javax.awt.event.AdjustmentEvent to call the javax.swing.adjustmentValueChange method of an java.awt.event.AdjustmentListener; AdjustmentListeners register with the JScrollBar by calling the scroll bar's addAdjustListener method.

The number of different classes in the Java (or other) GUI system is impressive. Strict and logical naming conventions are helpful guides, however either a fantastic memory or a good reference book is essential. Fortunately, these patterns of interaction are settling down into a universal framework, as developers gain more and more understanding of the requirements of any windowing system. In this way, learning one windowing system leads to knowing all windowing systems. There are differences, however, since their is not always consensus on the best ways of doing things, and even when their is approximate consensus, important details might vary. Hopefully, as our understanding of windowing systems improves, these differences will be conceptualized into easy to remember patterns, and those with both strengths and weaknesses will remain; those with only weaknesses will be abandoned.

Conclusion

As a final comment regarding Java's conceptualization, the registration of listeners is not the original AWT way of handling events, it was introduced in Java 1.1. This was not a change in the language, it was a change in the base class of libraries intended for application development. Originally, the hierarchy of components given by the containment of component within component to create the visual order was reused as the event passing structure. This was terrible. The two tended not to want the same organization, and one was forced to fit into the hierarchy of the other, causing many "phoney" components to be interposed into the visual hierarchy for the purpose of intercepting and re-dispatching events properly.

Although reusing the hierarchy was simpler, as there was but one hierarchy, it turned out to be more complicated, as it invited weird solutions to break from its limitations. A more general approach, using registered listeners, was rewarded down the line with simplier architectures where each hierarcy, the visual and the event, followed patterns individually best suited for each.


Last modified: April 13, 2004
Burton Rosenberg, copyright (c) 2004 all rights reserved.