Swing Features & Concepts

 

The Containment Hierarchy:

 

As we go through this section, we will use SwingApplication to see how the components of a GUI program fit together.

 

SwingApplication creates four of the most commonly used Swing containers and components:

 

 

 

 

Figure 1: Containment Hierarchy for SwingApplication

 

The frame is a top-level container and at the top of the containment hierarchy for SwingApplication. The JFrame provides a canvas on which the other containers and components will be painted.

 

The panel (JPanel) is a mid-level container and is used to position the Swing components  (JButton & JLabel) in the interface. We’ll be examining other and more interactive mid-level containers in later topics.

 

Lowest in the containment hierarchy are the atomic components, JButton and JLabel. These components present information to the user (JLabel) or gather input from the user (JButton) and provide some interactivity between the program and the user. The Swing API provides many atomic components.

 

Figure 1 shows each container created or used by the program along with the components they contain. Even the smallest Swing programs (like HelloSwingWorld) have multiple levels in its containment hierarchy. To view the containment hierarchy of any running program, select it (make sure it has the keyboard focus) and press CNTRL-SHIFT-F1. The standard output (typically the DOS window on Windows machines) will printout the containment hierarchy.

 

 

 

Figure 2: Containment Hierarchy for SwingApplication

 

Figure 2 shows a portion of the printout of the containment hierarchy of HelloSwingWorld. As can be seen, even the simplest programs have a complicated containment hierarchy. This also shows that there are many containers and components placed in the hierarchy automatically by Java.

 

As a general rule, the content pane holds all of the visible components within the window. The only exception to this rule is the toolbar, which is placed outside the content pane.

 

Components are placed in containers through the add method. Add methods have a least one argument – the component to be added. Occasionally there are other arguments that provide layout information. The code to add the button and label to the panel and the panel to the frame is:

 

frame = new JFrame(...);

button = new JButton(...);

label = new JLabel(...);

pane = new JPanel();

pane.add(button);

pane.add(label);

frame.getContentPane().add(pane, BorderLayout.CENTER);

 

Layout Management:

 

Layout management is the process of determining the size and position of components in the window. Every component has a default layout manager. The components may make suggestions as to how they would like the component to be laid out, but the Layout Manager makes the final decision. The five commonly used Layout Managers are:

 

 

CardLayout is a sixth special-purpose layout manager used in combination with other layout managers.

 

When using an add method the layout manager must be considered.  For example, the BorderLayout layout manager requires an additional argument and GridBagLayout a more elaborate setup procedure. Layout managers are usually used for two types of containers:

 

 

setLayout() overrides the default layout manager as in the following code:

 

            JPanel panel = new JPanel();

            panel.setLayout(new BorderLayout());

 

The above code forces the panel to use the BorderLayout layout manager. The container’s layout may also be set to null, which means that no layout manager will be used. In this case, absolute positioning must be performed. Absolute positioning is more difficult and tedious to code and does not adjust well when the user resizes windows and is sometimes unpredictable in differing operating systems.

 

Hints (layout suggestions):

 

There are a few methods that allow the developer to make suggestions as to the window’s layout. They are:

 

·        setMinimumSize()

·        setPreferredSize()

·        setMaximumSize()

 

Although the developer may use these methods to make suggestions, the layout manager makes the final decision. You may subclass a component and override its “get” methods (getMinimumSize(), etc) to gain more control over the layout. At this time, BoxLayout is the only layout manager that pays attention to the setMaximumSize() request. You may provide suggestions concerning layout through the alignment methods:

 

·        setAlignmentX()

·        setAlignmentY()

 

You may also extend a class and override its getAlignmentX() and getAlignmentY() methods. Again, BoxLayout is the only layout manager that pays any attention to the alignment hints.

 

There are three factors in determining the space between components. They are:

 

 

Layout Management (sequence of events):

 

First, the GUI is constructed through the execution of the appropriate code. The pack method is then invoked on the JFrame which may specify the frame’s preferred size. The layout manager will examine the size of the content frame in the top-level container and will then determine the content frame’s preferred size. If the developer specified the preferred size, it is at this point that it is reported to the layout manager. If the developer did not specify the preferred size, the current computer operating system’s Look and Feel is queried. The layout manager will begin at the bottom of the containment hierarchy to determine each component’s size and work its way up the containment hierarchy until all components have been examined for size and position. Eventually, the complete window’s size and position is determined and laid out for painting.

 

The layout manager will go through this process if the user decides to resize the window.

 

Event Handling:

 

Every mouse movement, mouse button click, or key press is an event. Any object may be notified of the event but it must first register as an eventListener for that event on that event source. To function properly, the object must implement the appropriate interface to handle the event.

 

 

Act that results in the event

Listener type

 

User clicks a button, presses Return while typing in a text field, or chooses a menu item

ActionListener

 

 

 

 

User closes a frame (main window)

WindowListener

 

 

 

 

User presses a mouse button while the cursor is over a component

MouseListener

 

 

 

 

User moves the mouse over a component

MouseMotionListener

 

 

 

 

Component becomes visible

ComponentListener

 

 

 

 

Component gets the keyboard focus

FocusListener

 

 

 

 

Table or list selection changes

ListSelectionListener

 

 

 

 

Every event is represented by an event object, which gives the listener information about the event and the event’s source. Event sources are typically atomic components such as buttons, scroll panes, text fields, and combo boxes, but they may be other objects as well. Event listeners may have multiple sources they are “listening” to, and sources may have multiple listeners waiting to handle an event.

 

 

 

Figure 3: Event source, objects, and listeners

 

Event Handler Implementation:

 

To create an event handler, the developer must write three sections of code:

 

1.      A declaration of the event handler class: code that declares a class that implements a listener interface or extends (subclasses) a class that implements a listener interface:

 

Example: public class myListener implements ActionListener { . . . }

 

2.      The event handler object must register itself as a listener on one or more components:

 

Example: myButton.addActionListener(instance of myListener);

 

3.      The code that handles the event must be written:

 

Example: public void actionPerformed (ActionEvent e) {

                        . . . .

                        }

 

Once the first two steps have been completed and executed, the program waits for an event to be dispatched (a button to be clicked, a selection made from a list, etc.). When the user initiates an event (clicks the button, makes a selection, strikes the enter key, etc.), the actionPerformed() method will execute.

 

We have briefly mentioned anonymous inner classes to handle event. Below is some code from SwingApplication that demonstrates this:

 

 

 

Figure 4: Action Event Handler

 

Note that to implement the anonymous inner class, that “new ActionListener( ) {” replaces “instance of myListener” in step two above. Immediately after the declaration statement, is the declaration of the actionPerformed( )  method (step 3 above). Within this method are the commands that handle the event. Anonymous inner classes are the preferred method for event handlers because they encapsulate the event handler.

 

Event handling code executes in the Event-Dispatching Thread. Doing so ensures that event handlers execute in order and that one finishes before the next begins. Your program could experience disastrous results if more than one event handler tries to execute simultaneously. Component painting and repainting is also handled in the event-dispatching thread.

 

Event handlers should execute very fast because the GUI is frozen while the actionPerformed( ) method executes. This means that no paints will occur during this time and that the GUI will not respond to keystrokes or button clicks during this time. Your program could be perceived as sluggish if the event handlers do not respond quickly.

 

Painting:

 

When a Swing GUI needs to be painted or repainted the Java platform begins with the topmost level container and works its way down the hierarchy. Swing components paint operations are handled by the Swing painting system, which is very efficient. It uses a double-buffering system, which means that the paint first occurs off screen – in a memory buffer – and is then flushed to the screen when complete.

 

Normally, Swing components paint themselves automatically. If this does not happen, you have some type of bug. The work-around for this is to call the component’s paint method manually. The repaint method should also occur automatically if the GUI is resized. If this doesn’t happen automatically, call the revalidate method on the component, and then call the paint method on the component. Painting is handled in the event-dispatching thread, so it also needs to execute quickly.

 

One way to speed up the paint operation is to make all components opaque. Swing then knows that it does not have to paint anything behind the component. Paint areas are always considered rectangular, but Swing allows components to have different shapes. To make the components appear to be different shapes, you should make them non-opaque. This informs the Swing painting system that the container behind the component needs to be painted in the background color. The non-opaque component can then appear to be any shape. The developer would also have to include some special code to adjust the “hit area” of the button so that it does not respond to clicks “outside” the button.

 

SwingApplication performs its painting in the following sequence:

 

1.      Top-Level container (JFrame) paints itself

2.      The content pane repaints itself and directs JPanel to repaint itself (JPanel is contained in the content pane, so is a child of the content pane). The content pane should be opaque to avoid messy repaints

3.      JPanel then repaints itself starting with its background and working out to its border. JPanel has to children (JButton and JLabel). JPanel then directs its children to repaint themselves

4.      JButton paints its background and then its text. If the button has the keyboard focus, it will perform some Look and Feel painting to let the user know this

5.      JLabel then paints its background and then its text.

 

In this way, Swing GUIs paint their backgrounds first and then work their way out to the foreground.

 

Threads and Swing: 

 

If GUIs are created and referred to in the correct way, you probably will not have to worry about threads. It is safe to construct applets in their init() method and applications should follow the following format:

 

 

 

Figure 5: Thread Safe Application

 

If your program must create threads to affect the Swing components then there are a few rules you must be aware of:

 

The Single Thread Rule:

Once a Swing Component is realized (is painted on the screen or ready to be painted on the screen) all code that might affect the component or depend on the state of the component should be executed in the event-dispatching thread. A component is realized by having one of these methods invoked on it:

 

 

Once a window or component is realized, all of its components are realized.

 

Exceptions to the rule:

A few Swing methods are thread safe and are noted in the Swing API documentation. Thread safe methods will be marked with this text:

 

            This method is thread safe, although most Swing methods are not.

 

As long as no components have been realized in the current run-time environment, it is safe to construct and show the GUI in the main thread (See Figure 5). In general, you can construct any GUI in any thread as long as you do not call any methods that refer to or affect already realized components.

 

Components of a GUI are realized by a call to the pack() method, and shown by the setVisible() method. Technically, using setVisible() after the call to pack() is unsafe because the components have been realized, but because the components have just been constructed, it is highly unlikely that a paint request will occur before the setVisible() method returns. No additional GUI code should be executed after setVisible() is called in main().

 

It is safe to construct an applet in its init() method. Let the start() method (the second method to be called on an applet) call the show() or setVisible() methods.

 

repaint() and revalidate() are safe to call from any thread.

 

Listeners (addListenerTypeListener or removeListenerTypeListener) may be modified from any thread.

 

Executing Code in the Event-Dispatching Thread:

 

Most post-initialization work is performed in the event-dispatching thread naturally.

 

Occasionally, some non-event GUI work must be performed after it is visible. For example, a program that takes a very long time to initialize, may want to show some of its GUI before initialization is complete, so the user doesn’t wonder what is happening. In this case, some GUI should show while the initialization continues and then, when complete, update the GUI. Another example is the case when a server gets a request from another machine. The method should update the server in the event-dispatching thread.

 

The Swing Utilities Classes provide two methods to help developers force code into the event-dispatching thread. They are:

 

 

As a general rule, invokeLater() should be used instead of invokeAndWait() whenever possible.

 

 


Send comments to: SJKuyath@uncc.edu
Copyright Stephen J Kuyath, UNC-Charlotte
last modified: February 10, 2002