Lectures on GUI and Swing, May 2011
Chris Johnson, Alexei Khorev
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).
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)
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:
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.
| Command-line application | GUI Application | |
|---|---|---|
|
|
|
| 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). |
GUI applications have important differences:
Type of events:
Polling (old, single-threaded)
Callbacks (modern, multi-threaded)
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):
| labels | radio-buttons | text widgets | menu-bars |
| separators | spin-buttons | text-field widgets | menus |
| menu items | scale widgets | list widgets | tree widgets |
| toggle-buttons | scroll-bars | progress bars | frames |
| message boxes | scrolled areas | forms | push-buttons | dialog boxes | status bars | toolbars | ⋅⋅⋅ |
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.
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).
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.
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.
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.
| 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 |
| 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 |
The general strategy of writing a GUI (View) part of the application can now be formulated as follows:
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.
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.
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).
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.
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:
/** 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)
... ...
Greater details from the Swing Tutorial:
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.
... ...
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.
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!:
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.
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");

Adding the menus is a little more work. Here are the basic steps:
Create a JMenuBar and link it to the window
Create one or more JMenu objects and add them to the menu bar
Create one or more JMenuItem objects and add them to the menus
Write an action listener class for each menu item, with the appropriate action coded into its actionPerformed() method
Create a listener object for each menu item
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.
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);
(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);
What GUI does?
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.
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.
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.
The Java API helps to implement the Observer pattern by providing Observer
and Observable types (both are in java.util package).
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()The application Clock consists of five classes:
update() to repaint
the the display panel; registered with model as observerAll 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.
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
java.awt.Graphics — the abstract base class for all graphics contexts that
allow an application to draw onto components that are realized on various devicesjava.awt.Graphics2D — the fundamental (also abstract) class for rendering
2-dimensional shapes, text and images; it extends the Graphics class to provide control
over geometry, coordinate transformations, color management, and text layout. 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).
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;
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));
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));
}
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());
}
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
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();
}
When compiled and run, the application displays the ticking "analog" clock just as you'd expected:

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.
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:
JComponent.paintComponent(Graphics) —
your application may need to override this method to achieve the custom
rendering ("effects" like gradient, opacity change etc);repaint()
call inside it — otherwise EQ will be stuffed!JComponent.paint(Graphics) —
Swing applications do not normally override this method (unlike AWT old ones);
but in some FRC apps this overriding is crucial since it allows you to set the graphics
state (used by a components and its children)JComponent.setOpaque(boolean) —
may need to be called on a component depending on whether the component's
rectangular bounds are not completely opaque (translucent); if so,
Swing will do the right thing for translucent components by rendering
contents behind the component as necessary (all Swing components except
for JLabel are opaque by default)
The presentation by the authors, Chet Haase and Romain Guy, at JavaPolis 2006,
Filthy
Rich Clients
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).
selected examples which are not captured in the podcast —
For storing and combining the colours of a drawing primitive into the destination (esp. AlphaComposite for translucency effects)
For visual enhancement, but also for more advanced effects like reflections and fade-outs
Blurs, reflections, drop shadows, highlights, sharpening
Motion, fading, pulsation, springs, morphing
Visual enhancements
More realistic motion simulation (in games, simulations etc)