Software Construction, SC for SE

Lectures on GUI and Swing, May 2011
Chris Johnson, Alexei Khorev


Graphical User Interface

Nowadays, all computer programmes which are intended for interaction with the human user are created with Graphical User Interface (while "In the Beginning was Command Line", by Neal Stephenson). According to Wiki: "allows for interaction with a computer or other media formats which employs graphical images, widgets, along with text to represent the information and actions available to a user. The actions are usually performed through direct manipulation of the graphical elements." The history of GUI development so far is very worth reading. Since standard computers have a well defined and constrained (less constrained now, after introduction of iPhone and iPad) interface, the type of GUI which can be deployed on them is often called WIMP (window, icon, menu, pointing device).

Xerox 6085 One of the very first GUI as it was created by Xerox 6085 ("Daybreak") workstation in 1985.  

Java had the facility to create Graphical User Interface for its programs as the part of the standard library from the very beginning. Originally, the package which contained necessary resources was Abstract Window Toolkit. A little later its drawing capabilities were extended by the Java2D API. Then (in Java 1.2) came Swing. Most Swing components are pure Java components or so-called lightweight components. That is, they are written completely in Java. AWT components are implemented natively on each different platform that supports Java. Somewhere deep down in the implementation, there is a bridge to the system's native graphics routines. Obviously this is different on Linux, Windows, Macintosh etc. As a result, these heavyweight components look different on different platforms.

However, as we shall see, even in a simple example of GUI program, the behaviour can exhibit subtle platform-specific differences (MouseSpy.java)

Java Foundation Classes

Swing is only one (though very large) package from the Java Foundation Classes, a metapackage used for building GUI and adding rich graphics functionality and interactivity to Java applications. The JFC content:

Java's GUI packages

Unfortunately many of the Java GUI classes have very large and confusing interfaces so that it can be hard to figure out what are the useful bits and what parts you can safely ignore (unlike with standard non-GUI interfaces). The Swing (and AWT) usage requires time for learning and experimenting, detailed tutorials and documentation. The Java Tutorial contains two sections on using Swing, one in the main trail, basic introduction, and one more advances, with many example of how to use Swing classes to fulfil major GUI programming tasks (creating windows, labels, widgets, text fields etc).

Java Swing (backed up by AWT) provides a rich set of GUI features including pluggable look and feel (plafs), shortcut keys, tool tips, support for assisting technologies and for localisation (or internationalisation). We won't have time to cover anything like all of it, but we'll do enough to get you started writing realistic graphical user interfaces. If time permits, we will demonstrate the usage of plaf, when the GUI appearance can be changed during run-time.

Swing isn't the only GUI library around. There is a smaller suite of widgets called Standard Widget Toolkit, which comes with the Eclipse platform (SWT and JFace are main GUI software components used in Eclipse). It's also free, and there are good on-line tutorials and the book about SWT ("SWT/JFace in Action" by M. Scarpino et al, Manning, 2005). Some believe that SWT is a better alternative to Swing, other countenance this with "So WhaT"? (Valid point after serious performance and design improvements of Swing in Java 1.6.) Another framework is QtJambi from (former Trolltech) Nokia — the makers of excellent Qt C++ GUI framework.

Program Architecture

Command-line application   GUI Application
Command line app           GUI app
Application has control at all stages of execution. User’s interaction with the application is pre-programmed.           After initialising the user-interface, application passes control over to the operation system. System waits for the user actions and other events, then call an appropriate function (event-driven programming).

Structure of graphical applications

GUI applications have important differences:

Type of events:

Event Handling Models

Polling (old, single-threaded)

Callbacks (modern, multi-threaded)

GUI Concepts

widget
a GUI component like a window, a push button, a label, a scroll-bar, a menu item...
pixel
a single dot on the display screen; displays typically have 800×600, 1024×768, 1280×1024 pixels
depth
the number of bits per pixel
  • depth 1 = black & white
  • depth 8 = 28 = 256 colours
  • depth 24 = 224 = 16,777,216 colours
colour map
translation between pixel values and actual screen colours on a low-depth display
bitmap
an image one pixel deep
pixmap
an image of arbitrary depth (effectively as a 2D array of pixels, although you may not be able to access them like array elements)
font
a mapping from character (or symbol) shapes to bitmaps

Widgets

Widgets are ready-made low-level (and not-so-low-level) components provided by the underlying library (e.g. Motif, MFC, GTK+, Java's AWT and Swing etc).

Examples of widgets include (the Java Tutorial contains a far more detailed description of the Swing controls and examples of their use):

labelsradio-buttonstext widgetsmenu-bars
separatorsspin-buttonstext-field widgetsmenus
menu itemsscale widgetslist widgetstree widgets
toggle-buttonsscroll-barsprogress barsframes
message boxesscrolled areasformspush-buttons
dialog boxesstatus barstoolbars  ⋅⋅⋅

First example — MouseSpy

Our first example include creating a simple graphical layout and simple event handling (for complimentary study — look into the Swing Tutorial starting with Getting Started with Swing).

The one class program we consider here is MouseSpy.java, which is converted from an applet code from The Big java book. The program puts a window on the screen and then catches all relevant mouse events and prints information about them on the standard output (System.out). We start with import declarations:

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;

Every class in the GUI part of your application must import all necessary classes and static methods from the relevant packages. Do not import everything (using the wildcard "*"), many Swing classes are very large.

What follows next is implementation of the MouseListener interface. Many (most) widgets in Swing can be used to pass data from the user to the application. This done via the mechanism of registering and responding to an event, which user (or some other agent) generates by interacting with the widget. Different types of widgets can react to a specified type of events, which are declared in a related listener interface. The GUI code must implement all required interfaces for the application to handle the required events.

MouseSpy: Event Listeners

public class MouseSpy implements MouseListener {	
   public void mousePressed(MouseEvent e) {
      System.out.println("Mouse pressed at (" + e.getX() + ", " + e.getY() + ")"); 
   }
   public void mouseReleased(MouseEvent e) {
      System.out.println("Mouse released at (" + e.getX() + ", " + e.getY() + ")"); 
   }
   public void mouseClicked(MouseEvent e) {
      System.out.println("Mouse clicked at (" + e.getX() + ", " + e.getY() + ")"); 
   }
   public void mouseEntered(MouseEvent e) {
      System.out.println("Mouse entered at (" + e.getX() + ", " + e.getY() + ")"); 
   }
   public void mouseExited(MouseEvent e) {
      System.out.println("Mouse exited at (" + e.getX() + ", " + e.getY() + ")"); 
}

public static void main(String[] args) {  ..... }

In the particular (very simple) case, the main application class itself implements the MouseListener interface, which will be registering five different events (declared by five methods in the interface), generated by the mouse input device. The complete list, description and example of usage of all major listeners in Swing is to be found in Listener API Table section of the Java Tutorial.

The listener methods define what actions take place as the result of event occurrence. Here, our only actions are printing some information on System.out stream. The MouseEvent has data like coordinates where it's happened. They can be established and (possibly) used. Apart from knowing its own coordinates, the event (MouseEvent, to be specific) can maintain the click count, characterise the type of component on which it occurred, and so on (the Java API java.awt.event documentation contains full nomenclature of what you can extract from an event).

MouseSpy: the main()

public static void main(String[] args) {
   JFrame frame = new JFrame(); 
   MouseSpy listener = new MouseSpy(); 
   frame.addMouseListener(listener);
   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   JLabel label = new JLabel("Hello world"); 
   JPanel panel = new JPanel();
   panel.setPreferredSize(new Dimension(300, 300));
   panel.add(label);
   frame.setContentPane(panel); 
   frame.pack(); 
   frame.setVisible(true);
}

Let's go through it like we did with HelloWorld.java in the very beginning.

JFrame frame = new JFrame(); // creates an application main window 

Frames know how to display themselves and their contents, how to minimise, maximise, move and change size. In the Swing Tutorial they are described in How to make frames (main windows).

MouseSpy listener = new MouseSpy(); // creates an event listener to monitor mouse events
frame.addMouseListener(listener); // attaches the listener to the frame (fixes geometrical area of events)

This code connects the listener with the GUI component (widget) it's listening for events on. In this case we're listening for events on the main window, but it can be any component. Later we'll see buttons: there you attach the listener to the button, a different listener for each button (usually, like in menu items). The relevant part of the Swing Tutorial is Writing event listeners.

MouseSpy: frame and labels

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

This statement tells the program what to do if the user closes the window (by clicking in the little X in one of the top corners usually, although the details of the appearance of the window and its decorations depends on the system and the window manager). Here we're saying that closing this window should quit the application. There are other possibilities: check the javadoc documentation for class JFrame for more.

JLabel label = new JLabel("Hello world");

This creates a label, which is a simple widget that can display a bit of text or an image, or both. This one just displays text. The constructor we've called sets the text of the label to the string “Hello world”. For more about labels, see How to use labels in the Swing Tutorial.

JPanel panel = new JPanel();

Another object creation. A panel is a container that you can pack other widgets into. We probably don't really need one here, but it helps with the next step. For more about panels, see How to use panels in the Swing Tutorial.

panel.setPreferredSize(new Dimension(300, 300));

This sets the preferred starting size of the panel. You can also tell a frame what size it should be, but this doesn't always have any effect. Try modifying the program to see. Anything that inherits from class java.awt.Component (including class JFrame and class JPanel) has a method setSize() that can take two integers or one Dimension object. Anything that inherits from class javax.swing.JComponent (including class JPanel) also has a setPreferredSize() method, but this only accepts a Dimension object.

MouseSpy: finishing touches

Sometimes setting the size of a widget works as expected, sometimes it doesn't. There's probably good design rationale for that, but damn be the user who must scour the documentation for literally thousands of classes. This is one of the hardest things about GUI programming. A good beginner's strategy for getting around it is to reuse chunks of code from other programs that work. Eventually you will want to understand everything you're doing, but initially you need to have some small successes, and reusing a few lines that work is a good way of getting that.

panel.add(label);

This puts the label inside the panel. This hides a detail: whenever you put widgets inside a Container (such as a JPanel) the way they are arranged is controlled by a Layout Manager. If you don't specify, then the default is what's called a Flow Layout, which means that components are packed in from left to right, top to bottom. This usually isn't what you really want. Later we'll see examples of some other layout managers: border layout and grid layout. For more about layout managers, see Laying out components within a container in the Swing Tutorial.

frame.setContentPane(panel); // (1)
frame.pack(); // (2)
frame.setVisible(true);	// (3)

The first makes the panel be the main display area of the frame (the “content pane”). Frames also have separate, specially defined areas at the top for menus and at the bottom for status bars or elsewhere. The second one is when the actual sizes and positions of everything in the window gets worked out. If you change the contents of a container and don't do this, the appearance doesn't change, which can lead to a lot of frustration. So if something isn't working, try repacking the frame and see if that fixes it. Lastly, the third instruction is needed because initially, when created, the frame is invisible. This is so that users don't see the messy process of adding components to the frame. Once everything has been put together and packed, you make the whole "concoction" visible as the last step.

Events and Event Handlers

Swing GUI Elements, Events and Event Handlers
General Category Generated Events Event Handler Interface
Mouse MouseEvent (dragging, moving)
MouseEvent (clicking, selecting, releasing)
MouseMotionListener
MouseListener
Mouse wheel Mouse wheel event MouseWheelListener
Keyboard KeyEvent (click, release) KeyListener
Selecting (item, checkbox etc) ItemEvent (Selection) ItemListener
Text Input TextEvent (new line entered) TextListener
Scrolling AdjustmentEvent (SB slider moved) AdjustmentListener
Buttons, Menu etc ActionEvent ActionListener
Window change WindowEvent (open, close etc) WindowListener
Keyboard focus change FocusEvent (component must have focus) FocusListener
Component change ComponentEvent (resizing, hiding etc) ComponentListener
Container change ContainerEvent (adding-removing component) ContainerListener

Swing Lightweight Control Elements

GUI Category Control Element Swing Class
Basic controls Button
Combo box, List
Menu
Slider
Toolbar
Text Fields

JButton, JCheckBox, JJRadioButton
JComboBox, JList
JMenu, JMenuBar, JMenuItem
JSlider
JToolbar
JTextField, JPasswordField,
JTextArea, JFormatTextField
Uneditable displays Label, Tooltip
Progress bar
JLabel, JToolTip
JProgressBar
Editable displays Table, Tree
Text
ColorChooser, FileChooser
ValueChooser
JTable, JTree
JTextPane, JTextArea, JEditorPane
JColorChooser, JFileChooser
JSpinner
Space-Saving containers Scroll pane
Split pane
Tabbed pane
JScrollPane, JScrollBar
JSplitPane
JTabbedPane
Top-level container Frame, Applet
Dialog
JFrame, JApplet
JDialog, JOptionPane
Other containers Panel
Internal frame
Layered pane
Root pane
JPanel
JInternalFrame
JLayeredPane
JRootPane

"Doing" GUI steps

The general strategy of writing a GUI (View) part of the application can now be formulated as follows:

  1. Create the widget. Depending on what type it is, you may want to pack it into a container, or pack other widgets inside it or whatever. Then display it on the screen.

  2. Create a ‘listener’ object to receive the event. This object must implement a listener interface (or extend one of the pre-supplied do-nothing adapter listener classes) from the package javax.swing.event or java.awt.event (usually the latter). You can create an object of a special class written just for this purpose, or you can add this capability to an existing object by adding extra code to its class.

  3. Connect this listener with the GUI component (widget) it's listening for events on. In this case we're listening for events on the main window, but it can be pretty much any component. Later we'll see buttons: there you attach the listener to the button — either its own listener for each button, or one listener to many buttons (or any widget which reacts to an ActionEvent).

  4. Write the code that must be executed when the event takes place. This must be in a special method specified by the listener interface. In most cases (where the listener is an ActionListener) this is just called actionPerformed(), but in the case of our mouse listener, there are several methods we have to implement, one for each different type of mouse event: down, up, click, enter and exit.

For more detailed and definitive information on this, see Handling events in the Swing Tutorial. For the full detail, see Writing event listeners in the Swing Tutorial.

Another example: TallyCounter

In this example, we have a separation of the Model part from the rest of the program, Counter.java.

class Counter {
	
   private int count;

   public Counter() {
      this.reset();
   }
   public void reset() {
      this.count = 0; 
   }
   public void increment() {
      this.count++;
   }
   public int getCount() {
      return this.count;
   }
}

It can be used by any client, eg by TestCounter.java which is run on the command line.

We want to use Counter in the "baby-GUI" application. Our first version, counter1, is the following:

TallyCounter : Buttons

/** This class is not a listener (as in MouseSpy), it's a JFrame */
class CounterApplication extends JFrame { 
	
   Counter counter; // gives access to Model's data and logic
   JLabel label; // GUI parts can be private if they are not used by outsiders
   JButton incrementButton, resetButton; // two button widgets

   public CounterApplication() { // constructor
      counter = new Counter();
      label = new JLabel("" + counter.getCount(), SwingConstants.CENTER); // label is created and positioned 

      incrementButton = new JButton("Increment"); // buttons created with names
      resetButton = new JButton("Reset");

      Container container = this.getContentPane(); // container to lay out buttons in the main window
      container.setLayout(new GridLayout(3, 1)); // type of layout can be different, GridLayout is chosen
      container.add(label); // label is added (it'll go on top)
      container.add(incrementButton); // first button is added (under the label)
      container.add(resetButton); // second button (at the bottom)
      ... ...

TallyCounter : Inner classes as event handlers

Moving on,

     ... ...
      class IncrementButtonHandler implements ActionListener {
         public void actionPerformed(ActionEvent event){ // the method implemented
            counter.increment(); // calling the model's method
            update(); // repaints the frame (defined below in the code)
         }
      }
     ... ...

A class within a class — an inner class. This is a new java construct which we hardly met before. It's a full Java class that just happens to be defined right in the middle of another class. When the Java compiler compiles this, it produces a separate .class file CounterApplication$1IncrementButtonHandler.class for this class. They are especially useful for GUI event programming. The inner class “knows” about all the local variables and fields that are in scope at this point, which saves quite a few lines of code.

This inner class has a name (which they don't all have to have). In this form it's the closest to what it would be like if it was in a separate file. We'll see some examples of other ways to use inner classes in other versions of Counter.

What does this inner class do? It's an action listener: a listener that reacts on event occurrence by taking an appropriate action. For a button, the action is that it is clicked on. It turns out that for most GUI components, the listener you need is an action listener, which is pretty much the simplest sort of listener. It only has one method you have to implement: actionPerformed() (MouseListner had five). Here when the user clicks on the increment button, we want to increment the counter and then update the display.

TallyCounter : attaching listeners

     ... ...	
      IncrementButtonHandler ibh = new IncrementButtonHandler();
      incrementButton.addActionListener(ibh);

      // repeating the same stuff for the reset button
      class ResetButtonHandler implements ActionListener { 
         public void actionPerformed(ActionEvent event){
            counter.reset();
            this.update();
         }
      }
      ResetButtonHandler rbh = new ResetButtonHandler();
      resetButton.addActionListener(rbh);

      this.setSize(150, 450); // setting the size of the frame
      this.setVisible(true); // making the frame visible
   } // the frame constractor ends

   public void update() { // updating the display by using the current value from Model
      label.setText("" + counter.getCount());
      repaint();
   }

There was no call to pack() — you need to call it with some layout managers, not others. You need it for border layout and flow layout, but apparently not for grid layout. If you want to know the details, look it up in Laying Out Components Within a Container in the Swing Tutorial. After that, you need to call repaint() on a component when the application's internal state has changed and it needs to be redrawn to reflect that change.

TallyCounter : voilá

Finally, the main:

   ... ...
   /* the main method */
   public static void main(String[] args){
      // creating the frame and the model
      CounterApplication app = new CounterApplication();
      // seen it before (could be placed in the constructor)
      app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    }
}

Compiling, running,... there!:

Counter application

Note: here the applet version of the Counter application version was meant to be displayed. But the latest (and the last) "upgrade" for JRE from Apple has introduced a bug which prevents correct simultaneous run of a Java Script and a Java applet (perhaps, it is a good thing that Apple will not support Java anymore). To run the applet, go to the examples directory, and the applet appletviewer CounterApplet.html.

TallyCounter : the title

In a modified version (from counter4), a title was added to the main window, and also a menu bar with two menus and mnemonics (keyboard shortcuts). Adding the title is just one line:

setTitle("Counter Application");

Improved tally counter application

TallyCounter : the menu

Adding the menus is a little more work. Here are the basic steps:

  1. Create a JMenuBar and link it to the window

  2. Create one or more JMenu objects and add them to the menu bar

  3. Create one or more JMenuItem objects and add them to the menus

  4. Write an action listener class for each menu item, with the appropriate action coded into its actionPerformed() method

  5. Create a listener object for each menu item

  6. Link each action listener to its menu item

For menus and menu items, you can set the keyboard shortcut with setMnemonic(). Once you've done this, you can use the keyboard to activate the menus. The exact keys involved varies from one system to another. On a Linux machine (Windowz too) one can increment the counter by typing Alt-A followed by I. On the Mac it's probably the Apple key instead of the Alt key.

Adding a menu

Here's the code for adding the menus to the counter application. To illustrate possible ways, each listener is implemented differently. The first one (for the Exit item in the File menu) is done with a named inner class, like in the first version of the counter application. The second one (for the Increment item in the Action menu) is done with an anonymous inner class. Neither the class nor the object has a name. The third one (for the Reset item) is done as a separate class in a separate file. You can judge for yourself which method you prefer. Does the shorter code sacrifice readability? Probably, not. One only has to get used to it, and then you will even like it.

JMenuBar menuBar = new JMenuBar();
this.setJMenuBar(menuBar);

JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic('F');

JMenuItem exitItem = new JMenuItem("Exit");
exitItem.setMnemonic('x');

// Handler implemented as named inner class
class ExitItemListener implements ActionListener {
	public void actionPerformed(ActionEvent event) {
		System.exit(0);
	}
}
exitItem.addActionListener(new ExitItemListener());
fileMenu.add(exitItem);

Menu item event handlers

(continued)

menuBar.add(fileMenu);

JMenu actionMenu = new JMenu("Action");
actionMenu.setMnemonic('A');

JMenuItem incrementItem = new JMenuItem("Increment");
incrementItem.setMnemonic('I');

incrementItem.addActionListener(
// Anonymous inner class to handle exitItem event
	new ActionListener() {
		public void actionPerformed(ActionEvent event) {
				counter.increment();
				update();
			}
		} // end anonymous inner class
		); // end call to addActionListener

actionMenu.add(incrementItem);

JMenuItem resetItem = new JMenuItem("Reset");
resetItem.setMnemonic('R'); // Handler implemented in separate class
resetItem.addActionListener(new ResetItemHandler(counter, this));
actionMenu.add(resetItem);

menuBar.add(actionMenu);

Recapitulating

What GUI does?

Anonymous inner classes

They can be used everywhere in Java, eg in creating a collection object which required implemented Comparator type parameter. The anonymous class is a particular example of an inner Java class. The inner class can be static, it can be private, it can be local to a class method (rarely used, eg for implementing Iterator interface inside a method for traversing a Collection type). The anonymous classes are often used in GUI programming.

    // w is a widget generating the OneEvent with which we register an event handler, it
    // in turn, implements OneListener interface by defining the method handleEvent(OneEvent e);
    // the event handler class is instantiated without a name and defined without a name
            .......     .........
    ge.addListener(new OneListener() {
                    public void handleEvent(OneEvent e) {
                        ...  do something ...
                    }
    } ); // the instance of the event handler is created and missing definitions are added on the fly

What is being achieved through the anonymous inner classes technique?

Using the anonymous inner class techniques a good practice only if the event handler is small (few lines of code). If it takes half a screen or more, it's better to make it in proper outside class of its own.

Model–View–Controller architecture

Model-View-Controller The Model-View-Controller architecture (MVC), has been first introduced in the revolutionary Smalltalk-80 platform (much like Java + NetBeans today — the language + environment + IDE — but 15 years earlier). The architecture is based on the concept of separating out an application from its user interface. This means that different interfaces can be used with the same application, without the application knowing about it. The intention is that any part of the system can be changed without affecting the operation of the other. For example, the way that the graphical interface displays the information could be changed without modifying the actual application or the textual interface. Indeed the application need not know what type of interface is currently connected to it at all.

The MVC components are:

The MVC is rarely used in its pure form. Swing design can be characterized as MVC in the most general sense: in Swing views and controllers are indivisible, implemented by a single UI object provided by the look and feel. The Swing model architecture is more accurately described as a separable model architecture. The Swing model architecture is described in the Swing Architecture Overview.

Observer design pattern

Observer DP When the GUI application is designed with clear separation of data/logic part (Model) and representation part (View), it's important to maintain the consistency between the separated classes. But for the sake of reusability, the classes should not be tightly coupled. (Of course, this is a more general problem which arises in all kind of applications, not just GUI.) The OO design has a solution to creating a loosely related components (classes), such that a change in one of them (subject, or observable) changes, the other(s) (observers) is notified, and as the result the observer queries the subject for synchronization of its own state with the subject state. The number of observers is not limited and can change dynamically. The subject does not depend on observers: When the subject changes its state and notifies the attached observers, it doesn't need to know how many of them are, and what they are. The strong coupling is avoided because the observer does not have to watch the subject closely, instead it gets notified when a change in the subject state occurs. This solution is known as Observer (design) pattern.

The Observer is one of many design patterns which developers use for creating software applications. This DP approach was popularized by the so called Gang-Of-Four (E.Gamma, R. Helm, R. Johnson and J. Vlissides) in 1994. Their original book cataloged 23 different DPs on the area of general OO design. Since then, the number of patterns and the area of their applications (distributed and parallel systems, real-time systems etc) has increased considerably.

Observer and Observable

The Java API helps to implement the Observer pattern by providing Observer and Observable types (both are in java.util package).

Observable
this concrete class contains several methods
  • public void addObserver(Observer o) (there is also a method for counting registered observers)
  • public void deleteObserver(Observer o)
  • public void notifyObservers() (+ one overloaded with a single Object parameter)
  • protected void setChanged() (there is also a protected method clearChanged())
  • public boolean hasChanged()
An application subject (model) class can extend the Observable class, override the above methods and add more methods (see the Clocks examples).
Observer
this interface declares a single method
  • void update(Observable o, Object arg)
This method is called whenever the observed object is changed. An application calls an Observable object's notifyObservers() method to have all the object's observers notified of the change.

The design of the Clock application

The application Clock consists of five classes:

  1. Clock.java — the application main class; creates model and view, and registers the latter as an observer to the model; also creates the controller object (which implements the event handler and creates timer object which will periodically generate model's state changing events)
  2. Model.java — the application's Model, maintains and operates with time data; extends the Observable class; in this version, the observable model has one Observer view, which gets notified every time the model state changes
  3. View.java — responsible for representing the time in a graphical form, the application's View; implements the Observer interface by making update() to repaint the the display panel; registered with model as observer
  4. Controller.java — links model and view, and determines the occurrence of timer events (both timer and panel generate ActionEvents), and defines the event handler (ActionListener) for responding to events by triggering change in model's state
  5. AnalogClockPanel.java — used by View to create a displayable graphical representation of the analog clock; here is where Java2D package is used to do the actual drawing (this class will be discussed specially); it uses the model data to determine the position of hour-minute-second hands

All classes are implemented in a straightforward manner, and there is nothing surprising here. We shall have a little to say at the end about Controller.java (meaning of Timer object), so we spend most of the attention to the "drawing" class AnalogClockPanel.java as it heavily uses the drawing utilities of the Java2D package.

Java 2D

The Java 2D API allows to enhance GUI by supporting the following tasks:

The Java Tutorial has a section of Java 2D, which can be used for additional studies. Some Java 2D classes are located in the package java.awt.geom. The package provides resources for editing and rendering (creating pixmap on screen or on printable media) line art, text, and images.

Two important classes used in "2D" programming are in the java.awt package

JPanel, paintComponent()

The class inherits from JPanel and "has-a" Model reference to model instance to know where to draw the clock hands.

public class AnalogClockPanel extends JPanel {

   Model model;
   public AnalogClockPanel(Model m) {
      model = m;
      setPreferredSize(new Dimension(200, 200));
      setBackground(Color.white);
   }

   public void paintComponent(Graphics g) {
      super.paintComponent(g);
         ...... .......
   }
... ...	

The constructor content is familiar (no comment is necessary). The rest is the overriding paintComponent(Graphics g) method of JPanel class, which in turns is inherited from javax.swing.JComponent. The call super.paintComponent(g) is done to call UI delegate's paint method (which is responsible for reproducing look-and-feel features). The rest of the paintComponent method defines the actual operations which result in clock face and hands being painted.

Remarks: avoid confusing with almost identical method paintComponents() available from Component which is a part of AWT. We do not override methods paint() or repaint(), since doing it would make all sorts of things to go wrong (can't go into details, just take API instructions on faith, and study the Swing programming — you will need a good book).

Graphics Context

What is this argument Graphics g that gets passed? This is called a “graphics context”. It keeps track of all sorts of details of how we're doing graphics right now: the background colour, the foreground colour, the line thickness, the current font and so on. In the code that follows we sometimes change the state of the graphics object. Graphics is an abstract class with only two subclasses. One will be used — Graphics2D). Most methods of the Graphics class can by divided into two basic groups: draw and fill methods (enabling you to render basic shapes, text, and images), and attributes setting methods (which affect how that drawing and filling appears). When we actually draw something, the graphics object is the target, not the component we're drawing onto. This may seem to be quite confusing.

So, without calling super.paintComponent(g);, as API warns, we will likely see visual artifacts (doesn't happens here, but one has to be consistent). Next we need to get the dimensions of the area we're drawing the clock face onto:

Rectangle bounds = getBounds();

The actual Graphics type we are working with here is Graphics2D, to get additional features we need to recast:

Graphics2D gg = (Graphics2D) g;

and use gg 2D graphics context from this moment on. The next step is to locate the co-ordinates of the centre of the panel.

int x0 = bounds.width / 2;
int y0 = bounds.height / 2;

Manipulating Graphics Context

We want to draw the biggest clock face that will fit inside this window. The variable size will represent the radius of the largest circle that will fit.

int size = Math.min(x0, y0);

Graphics2D objects can perform co-ordinate transformations. Instead of having to write out the code for all sorts of complicated geometry (see below), we could just translate and scale everything so that the centre of the clock face was the origin and the radius was 1. It would be nice, but unfortunately it doesn't work for drawing text, only for lines and shapes. So we could use this for the hands and the tick marks, but not for the numbers. So in the end in the end co-ordinate transformations were not used. It would be even more confusing to have two different sets of co-ordinates in use at the same time. But it's a useful thing to know about, and it might come in handy for something else some time.

//gg.translate(x0, y0);
//gg.scale(size, size);
//gg.setStroke(new BasicStroke(0));

This next bit sets the line width for drawing. This gives you a thin line.

gg.setStroke(new BasicStroke(1));

Clock Face: a little of mathematics

Now the code to draw the tick marks around the outside of the clock. There's a bit of coordinate geometry here, but you should be able to understand it. The loop takes n from 0 to 59, so there's one iteration for each of the 60 points around the clock face. Each of those points is 6° around from the last one. Angles in mathematics are measured anti-clockwise from the positive x-axis, so the angle around the circle, in degrees, of the nth tick mark should be (90 - 6n). Java deals with angles in radians, so that has to be multiplied by 180/π. The small tick marks go from 0.7 to 0.75 of the size. The long tick marks go from 0.65 to 0.75 of the size. Finally, Java's coordinates have the x-axis as you would expect from mathematics, increasing to the right, but the y-axis is the opposite, it increases down the screen, rather than up. That's why the calculation for the y coordinates of the endpoints of the tick marks involves y0 - r⋅sin(Θ) , but not '+' as in the standard rotation formula.

double radius = 0;
double Θ = 0;

for (int n = 0; n < 60; n++) {
	Θ = (90 - n * 6) / (180 / Math.PI);
	if (n % 5 == 0) {
		radius = 0.65 * size;
	} else {
		radius = 0.7 * size;
	}
	double x1 = x0 + radius * Math.cos(Θ);
	double y1 = y0 - radius * Math.sin(Θ);
	radius = 0.75 * size;
	double x2 = x0 + radius * Math.cos(Θ);
	double y2 = y0 - radius * Math.sin(Θ);
	gg.draw(new Line2D.Double(x1, y1, x2, y2));
 }

Fonts: shapes and sizes

Now to drawing the numbers. We have to specify the font. Without going into too much detail, I choose a simple sans-serif font here, with the size increasing in proportion to the size of the window. The tricky thing here is drawing a string with its centre at a particular point. For convenience of writing text on the screen, strings have a reference point which is on the left edge at the baseline (the imaginary line that characters sit on, and that some characters like ‘g’ descend below). To be able to put the centre of a string at a particular point (0.9 of the size out from the centre of the window) we need to get all the dimensions of our string and do a translation.

  Font font = new Font("SansSerif", Font.PLAIN, size / 5);
  gg.setFont(font);
  for (int n = 1; n <= 12; n++) {
      Θ = (90 - n * 30) / (180 / Math.PI);
      radius = 0.9 * size;
      double x1 = x0 + radius * Math.cos(Θ);
      double y1 = y0 - radius * Math.sin(Θ);
      String s = "" + n;
      // To centre the numbers on their places, 
      // we need to get the exact dimensions of the box
      FontRenderContext context = gg.getFontRenderContext();
      Rectangle2D msgbounds = font.getStringBounds(s, context);
      double ascent = -msgbounds.getY(); // not used here, but what's the hell
      double descent = msgbounds.getHeight() + msgbounds.getY();
      double height = msgbounds.getHeight();
      double width = msgbounds.getWidth();

      gg.drawString(s, (new Float(x1 - width/2)).floatValue(),
                    (new Float(y1 + height/2 - descent)).floatValue());
  }

Lines and Colours

Drawing the clock hands is just more of the same. Here, a float argument to BasicStroke() is used to get finer control over the thickness of lines (and it's OK), but it doesn't have much effect. Those numbers are numbers of pixels, so one can't draw a line 1.5 pixels wide. Setting the thickness to zero doesn't give you an invisible line, it gives you the thinnest line the system can draw, which is (unsurprisingly) one pixel wide.

      // Draw the hour hand
      gg.setStroke(new BasicStroke(2.0f));
      Θ = (90 - (model.hour + model.minute / 60.0) * 30) / (180 / Math.PI); // model data are used
      radius = 0.5 * size;
      double x1 = x0 + radius * Math.cos(Θ);
      double y1 = y0 - radius * Math.sin(Θ);
      gg.draw(new Line2D.Double(x0, y0, x1, y1));

      // Draw the minute hand
      gg.setStroke(new BasicStroke(1.1f));
      Θ = (90 - (model.minute + model.second / 60.0) * 6) / (180 / Math.PI); // model data are used
      radius = 0.75 * size;
      x1 = x0 + radius * Math.cos(Θ);
      y1 = y0 - radius * Math.sin(Θ);
      gg.draw(new Line2D.Double(x0, y0, x1, y1));

      // Draw the second hand
      gg.setColor(Color.red);
      gg.setStroke(new BasicStroke(0));
      Θ = (90 - model.second * 6) / (180 / Math.PI); // model data are used
      x1 = x0 + radius * Math.cos(Θ);
      y1 = y0 - radius * Math.sin(Θ);
      gg.draw(new Line2D.Double(x0, y0, x1, y1));
   } // end of paintComponent() method
} // end of AnalogClockPanel class

Using Java 2D: the rest of the classes

The Model class uses Calendar instance which it recreates every update() to get the current values of the hour-minute-second. This class from java.util package also allows to compute all possible time and date related information (year, month, day-of-week, date, AM or PM etc). Study the java.util.Calendar API to learn how further to use this class, since this will be required in the Assignment 2.

The Controller class uses Timer (from java.swing.Timer). After instantiating the listener, which implements ActionListener by telling the model to update, it creates the Timer object timer, which "fires" every 100 msec, and links it with the listener. As the result, every time the timer activates the listener, the latter asks the model to update(), which entails asking for the exact time using a Calendar object, storing the hour, minute and second in model's fields, comparing them with the old second value, and, if different, notifying the Observer view, what finally causes it to repaint the display.

   // constructor from Controller class
   public Controller(Model m, View v) {
       model = m;
       view = v;
       
       listener = new ActionListener() {
           public void actionPerformed(ActionEvent e) {
               model.update();
           }
       };
       
       timer = new Timer(100, listener);
       timer.start();
   }

Clock application

When compiled and run, the application displays the ticking "analog" clock just as you'd expected:

Clock application

Swing threads etc
advanced notes, not for S W Construction course lectures

Threads in Swing applications

Any work which results in GUI modification that is initiated by an application must be processed on EDT. Every request (response to an event) is wrapped into an event object and posted onto the event queue.

Swing painting

It's a process of an application updating the display, either explicit — executed through instructions from your code (custom painting) , or implicit — through Swing's internal code (window events of resizing etc are coded this way). A paint request is posted on the EDT and results to methods paint() (for asynchronous paint requests) and paintComponent() (for synchronous ones). The paint() method shouldn't be normally called in a Swing program, instead Component.repaint() is to be used.

Swing rendering

Swing painting When the paint event is dispatched from the EDT, it's sent to the RepaintManager object, which call paint() method(s) on the appropriate component. This paint call results in a component first painting its own content, then borders, and then any components it may contain (its children). The entire hierarchy of components, from the top JFrame down to the last button and menu item, is rendered. This is a back-to-front method of painting, where the "backmost" component (JFrame) is rendered first, then the items in that component (eg, JPanel, JMenuBar…), then the items in those components (JLabels, JMenus, JButtons…), and so on, until finally the "frontmost" components are rendered too.

"The trick is figuring out where your application needs to plug into this system (of paint calls) to get the right stuff painted at the right time." (FRC, p. 21) Three important things:

(Filthy) Rich Client Applications

Filthy Rich Clients

The Podcast

The presentation by the authors, Chet Haase and Romain Guy, at JavaPolis 2006,
Filthy Rich Clients

The Book

The systematic techniques supported by various frameworks (like AnimatedTransitions and TimingFramework) were developed by the two authors and presented in their book Filthy Rich Clients.

JavaFX™ to some extend is a further evolution of this approach (albeit based on a new scripting language).

The Demo

selected examples which are not captured in the podcast — demo

Rich Client Application Features

Composites

For storing and combining the colours of a drawing primitive into the destination (esp. AlphaComposite for translucency effects)

Gradients

For visual enhancement, but also for more advanced effects like reflections and fade-outs

Static Effects

Blurs, reflections, drop shadows, highlights, sharpening

Dynamic Effects

Motion, fading, pulsation, springs, morphing

Transitions and animations

Visual enhancements

More realistic motion simulation (in games, simulations etc)

3D effects