There are times when you want a modal window that implements window-specific modality rather than the application-wide modality provided by the standard JDialog class. This article explains the workings of the JModalWindow project, which provides two top-level components, called ModalWindows, that introduce such modality. The first ModalWindow class, JModalWindow, is a subclass of JWindow that's generally used for dialogs that block other windows. The second, JModalFrame, is a subclass of JFrame that can be used either as a blocked window or as a blocking window. Both classes implement an interface named InputBlocker.
Why create a ModalWindow instead of using the standard JDialog component? We were working on a pension-planner application to give users insight into their financial situations before and after retirement. This was done with the aid of a financial graphic. Throughout the application, help information for various pension-specific terms was available in a separate help window. In order to make comparisons, the user should be able to change different variables that influence pension savings. While doing this, the underlying financial graphic had to be blocked. A JDialog could have been used, but then the necessary help window would have also been blocked, and thus useless. Furthermore, the title bar presented by JDialog didn't fit in with the rest of the look and feel of the application.
I searched the Internet for a possible solution for our problem and found several suggestions, but they all had drawbacks or loopholes. That is why I decided to create my own modal window implementation based on the ideas that came closest to our needs. The JModalWindow project is an open source version of our solution to this challenge and is part of the JavaDesktop community at java.net.
Parent and Child Windows
It helps to have a common language when referring to the various windows involved. The window to be blocked is known as the parent window or owner; another term for blocked is busy. The window blocking the parent is the child window. A parent window can have more than one child window blocking it. Each child can block its parent and, in recently added functionality, some additional windows. Ideally, the parent window and the child window should both be ModalWindows; at the very least, they should both implement the InputBlocker interface.
An example will clarify these concepts. Here is a snapshot of a GUI produced by a JModalWindow that is
modal with respect to a JModalFrame:
Figure 1. A snapshot of a GUI produced by a JModalWindow that is modal with respect to a JModalFrame.
As the preceding figure shows, a JModalFrame's busy status is
marked using a blur effect and stop cursor. You can customize the following
aspects of the display:
Stop cursor: By default, this is a stop sign, but you can specify
a custom cursor.
Busy effect: The built-in effects are raster or line.
Initial position of dialog: The dialog can be initially centered
onscreen, centered over a window, relative to a component, or at a specific
X,Y location.
The following figure shows the same GUI as the preceding one, but with a line
busy effect, custom stop cursor, and initial Y position relative to the
bottom of the Update button that brought up the dialog. The X position
of the dialog depends on the JModalWindow release. The dialog is
be positioned at the same X position as the button, as long as the dialog
stays onscreen; otherwise it will be moved to the left to keepWindowCompletelyOnScreen.
Figure 2. The same GUI as the preceding one, but with a line busy effect, custom stop cursor, and initial Y position relative to the bottom of the Update button that brought up the dialog.
The following code creates a dialog that is modal relative to the window (owner)
containing returnFocusComponent and that is optionally placed relative
to returnFocusComponent. The dialog has a single button, which
closes the dialog.
For the best results, the owner should be a ModalWindow.
In the preceding code, the JModalWindow constructor sets up the
dialog, and the relativeToOwnerChild method causes the dialog to be
positioned just under the returnFocusComponent's onscreen representation.
The InputBlocker Interface
Both JModalFrame and JModalWindow implement an interface
called InputBlocker. The interface defines the following two methods:
public boolean isBusy();
public void setBusy(boolean busy, Window blockingWindow);
With those methods, it is possible to check whether a window is currently blocked
and let any child window signal that it is blocking or unblocking its parent.
Note: The draft version of this article was written based on the sources
of version 1.02. But since then I have added some new functionality that is detailed
in the section Latest Developments and Ideas. One
of those new functionalities led to a third method definition in the InputBlocker
interface:
public void addAdditionalModalToWindow(Window window);
With this method, it is possible to add additional windows that should be blocked
at the same time as the parent window. This is the reason why in the next paragraphs
the variable modalToWindows is plural instead of modalToWindow,
which you would expect because a child window only has one parent window.
The isBusy and setBusy methods are useful for the
parent window; the addAdditionalModalToWindow method is useful
for the child window.
Visualization and Blocking
To block all keyboard and mouse action for the underlying window, a JBusyPanel
object can be used as a GlassPane for the blocked window. The JBusyPanel,
when activated, consumes all keyboard strokes for the underlying window and
grabs the focus for any mouse action on it.
To make it obvious that the window is blocked, you can apply one of two types
of blurring:
BLUR_STYLE_LINE
Blurring with horizontal lines. A bit of a legacy style, because the first
raster implementation (drawing the separate dots) was too slow on older computers.
BLUR_STYLE_RASTER
Blurring with diagonal, parallel lines from top left to bottom right, which
leads to a raster effect.
The desired blur style, gap, and color are applied by the paint
method of the JBusyPanel. When blocked, the ModalWindow also
changes the shape of the cursor (see getBusyCursor).
To block the FocusManager in JavaTM 1.3, JBusyPanel overrides
the deprecated method isManagingFocus.
Construction of ModalWindows
The constructors for JModalWindow and JModalFrame
accept the following parameters, as needed:
Window owner
To set the parent window for the newly constructed window.
Component returnFocus
To set the component that should get the focus when this window is closed.
String title(JModalFrame only)
To set a title for the frame.
boolean modal
To change the default modal setting for the window.
Each ModalWindow constructor performs the following actions:
The appropriate super is called to set the title
for the JFrame and the owner for the JWindow.
(Note: If no owner is specified, the Swing SharedOwnerFrame
is used.)
The optional returnFocus is stored.
The list modalToWindows is set to contain the supplied owner
when this Window is constructed as being modal. Because the parent
isn't blocked yet, the status variable notifiedModalToWindow
is set to true.
The list with blockingWindows is initialized. This Vector
is used to make it possible to have multiple modal child windows.
The JBusyPanel that will block this window is initialized with
the following settings:
A color that is a bit darker than the default white background color.
A fixed BLUR_STEP of 2.
A BLUR_STYLE of the type raster.
These settings were chosen because that combination looks best, in my humble
opinion. However, if performance is an issue, these values can be changed
in your own copy of the source code.
Finally, the WINDOW_EVENT_MASK is used to activate the processing
of WindowEvents.
Each JModalWindow also performs the following initialization:
Creates a raised border to indicate that the window can be dragged.
Activates MOUSE_MOTION_EVENT_MASK for the processing of MouseEvents,
to support dragging of the window.
Each JModalFrame performs the following additional initialization:
Sets up a MinimumFrameSizeAdapter to keep the frame from being
resized smaller than minWidth by minHeight when those
values are supplied through the method setMinSize.
Checks whether a default icon, for which the resource location can be filed
under the key swingx.frame.icon in the UIManager,
is available. If this is the case, the support utility is used to fetch it.