Activation and Blocking of the Parent Window
As soon as a child ModalWindow is activated through a call to the method
show, it checks whether it is modal to a parent window stored in
the list modalToWindows. When this is the case and the parent window
implements the interface InputBlocker, its setBusy
method is called to notify that parent window that it is blocked by this child
window. Should the parent window not implement the interface InputBlocker,
then the parent window is only disabled.
Unlike the JDialog behavior, calling the method show
doesn't halt the calling thread. To create the same effect, call the method
wait_for_close after calling show. (Note: This method will be renamed to waitForClose in a later release.)
Note: Activating the window with a call to the method setVisible(boolean visible)
results in an indirect call to the method show. That is the reason why the situation for visible is true isn't handled in the setVisible method itself.
Placement of the ModalWindow is aided with the following helper methods:
centerOfScreen, centerOfOwner, and relativeToOwnerChild.
(See support utility for a description of these methods.)
Deactivation and Unblocking of Parent Window
Upon closing a child ModalWindow through a WindowEvent.WINDOW_CLOSED
(see the methods processWindowEvent and close), or a call
to the method setVisible, the ModalWindow calls the method
restoreOwner to check whether it is modal to a parent window stored
in the list modalToWindows. When this is the case, the parent window
is notified that this child window is no longer blocking it by a call to the
parent window's method setBusy or, if the parent window doesn't
implement the InputBlocker, by enabling it.
Because a call to setVisible(false) results in a WindowEvent.WINDOW_CLOSED
event and thus in multiple calls to the method restoreOwner, the
variable notifiedModalToWindow is used to prevent multiple calls
to the parent window's method setBusy. This helps when the parent
window's setBusy implementation doesn't handle multiple invocations
by the same child window well. (My first implementation, for example, decremented
a blockedCounter.)
If the parent window is no longer blocked by any child window, the focus is
returned to the optional returnFocus component, if it's supplied.
Finally, a call to the method release is made to notify any waiting threads that were halted by a call to the
method waitForClose.
Blocked by Child Window
When a ModalWindow is blocked by a child window calling its setBusy method, the following actions are
taken:
Retrieve the default or the custom busy cursor.
If the ModalWindow is not already blocked, save the current cursor
and set the cursor to the busy cursor.
JModalFrame only: Save current resizable status
if the frame is not already blocked, and disable the resizing of the frame.
Enable the JBusyPanel glass pane.
Add the calling child window to the list of blockingWindows.
Force the glass pane to get the focus so that it consumes KeyEvents.
Set the cursor for the glass pane to the busy cursor.
Note: Setting the window cursor and the glass pane cursor in this order
works around the Win32 problem where you have to move the mouse one pixel
to get the cursor to change.
Behavior when Blocked
To check whether a ModalWindow is currently blocked, a call to the method
isBusy results in a confirmation if there are any known blockingWindows. As soon as a ModalWindow is blocked, its behavior changes in the
following situations:
Unblocked by Child Window
When a ModalWindow is unblocked by a child window calling its setBusy
method, the following actions occur:
Remove the calling child window from the list of blockingWindows.
Reset the cursor for the glass pane to the old cursor and disable the glass
pane.
Try to retrieve focus in the ModalWindow.
JModalFrame only: Reset the resizable status
to the stored wasResizable value.
Reset the ModalWindow's cursor to the old cursor.
Note: Setting the glass pane cursor and the window cursor in this order
works around the Win32 problem where you have to move the mouse one pixel
to get the cursor to change.
Window Drag Support
To support the dragging of a JModalWindow, the method processMouseMotionEvent
checks the following things:
checkDragZone
- If the mouse is moved near the edge of the window (based on the value of
the variable
DRAG_BORDER_DISTANCE; currently only a value of
1 seems to work properly), then the cursor is changed to the MOVE_CURSOR
to indicate the possibility of dragging the window across the screen, and
the current mouse position is stored in priorDragLocation.
dragWindow
- If the mouse is dragged, and the cursor has the
MOVE_CURSOR
shape and a priorDragLocation is available, then the window is
moved along the delta x and delta y relative to the current
mouse position, and the priorDragLocation is reset to null
to signal that the last mouse-drag event is handled completely. If there was
no priorDragLocation available, then the current mouse position
is saved for the next mouse drag event.
Decorated Window Border
To decorate JModalWindows with a raised border the following methods
are overridden: paint, setBackground, and setForeground.
Support Utility
The Utils class handles various handy functions for JModalWindow
and JModalFrame:
getBusyCursor
- Retrieves the busy cursor. You can define a custom busy cursor under the
key
swingx.busy.cursor in UIManager. A Win32 problem
requires the cursor to be 32 x 32.
getIcon
- Retrieves the specified image resource as an icon. If the resource can't
be found, returns the missing image icon.
getStopImage
- Creates a stop sign as the default busy cursor.
getMissingImage
- Creates a red cross on a white background to indicate that the specified
icon/image couldn't be found.
centerOfScreen
- Positions a window in the center of the screen.
centerOfOwner
- Positions a window so it's centered above the parent window. This method
uses some slack at the edges of the screen, with the currently hard coded
variable
SCREEN_SAFETY_MARGIN, just in case of operating system
toolbars situated there.
relativeToOwnerChild
- Positions a window just below the supplied component, similar to how a combo
box's drop-down list is positioned. This method also uses some slack at the
edges of the screen with
SCREEN_SAFETY_MARGIN.
keepWindowOnScreen
- Positions a
Window at the specified x,y location,
adjusting the location if it results in the window falling outside of the
screen and becoming unreachable. This method also uses some slack at the edges of the screen with SCREEN_SAFETY_MARGIN.
(Note: This method will be renamed to keepWindowPartiallyOnScreen because of the introduction of another method called keepWindowCompletelyOnScreen.)
The Utils class also provides the following functionality, which
isn't currently used by the JModalWindow and JModalFrame
classes:
updateComponentTreeUI
- Updates the component tree on changes in the look and feel. It works from the bottom up
instead of the top down as in the Swing version, because some of our required
look and feel changes otherwise didn't show without user intervention.
Latest Developments and Ideas
Recent additions to the JModalWindow project have been guided by discussions in a variety of forums. For example, one question on whether it is possible to make a dialog modal for some frames and non-modal
for others led to the addition of the method addAdditionalModalToWindow(Window window) to the InputBlocker interface (link to post ).
A comment on a Spanish-language forum pointed out an additional use of modal windows due to the feature that a blocked JModalFrame, upon activation, automatically moves the blocking child window to the front. (Note: In a later release this feature will also be added for the mouse click in a blocked JModalWindow.) Here is a translated excerpt:
When using a modal dialog in a Swing application the following
undesirable effect takes place: when the user changes to another application
and back to the Java application, the modal window is hidden under the main
window. Due to the nature of modality, you cannot interact with the main one, and
to top it all, the only way to return to the modal dialog is to use the combination
of Alt+Tab keys (
link to original ).
This discussion also triggered me to complete the project with a JModalDialog
with the same functionality as the JDialog; in other words, blocking
all active frames and windows, but using the same approach as the JModalFrame
and thus creating the same visual effect. Because the InputBlocker now had
the additional method addAdditionalModalToWindow(Window window),
it was possible to create a simple JModalDialog by extending it
from the JModalFrame and just markAllWindows(), except
for the dialog itself and the Swing shared owner frame, to the list of windows
that must be blocked. For a JModalDialog to work properly, all used
windows should be ModalWindows or, at the very least, implement InputBlocker,
because you can call setEnabled(false) only so many times.
Another user asked in a forum posting if I had noticed a severe flicker when one or two modal windows are opened. Once I knew where to look, I saw a rather annoying flicker that I had never noticed before. The flicker was caused
by the call to setResizable(false). That is why I changed the way
resizing is prohibited when the frame is blocked (link to post ).
As a result of an issue reported on a problem with the use of window positioning relativeToOwnerChild in a dual-monitor environment, I added support for the GraphicsConfiguration and the use of its supplied getBounds() method for correct window positioning.
The following cosmetic changes were made, which in some cases provide more leeway than one would normally expect for modal windows:
- To keep the duplication of code to a minimum, a helper class named
JModalHelper was introduced.
- Resizing and moving of blocked windows was enabled.
- The method
activateFirstAvailableBlockingWindow(WindowEvent windowEvent) was added to the InputBlocker for the prior mentioned feature that a blocked JModalFrame upon activation automatically moves the blocking child window to the front.
- To enable the use of "Show Desktop", it is necessary that a blocked
JModalFrame is allowed to be iconified.
- A fix for the incomplete repainting of a frame that sometimes occurs when a frame is de-iconified immediately after it was iconified. When the frame is first moved
toFront before is it de-iconified, this problem doesn't manifest itself anymore.
If you want to find out about some of the dirty details that led to some of the choices, just check the
issue list .
Conclusion and Credits
As always, if a standard Swing component meets your needs, you are encouraged to use it. In the case where you are looking for a window that does not block the entire application but is modal only with respect to some of your open windows, you may want to consider using JModalWindow. Visit the JModalWindow project home. I look forward to your feedback and suggestions for further enhancements and comments on how you may be using this project in ways that we may not have anticipated.
Finally, if you want to use modal windows with JavaTM 1.3, contact me for the 1.3 source code. If there is enough interest we could also create a project branch for a 1.3 version.
A runnable .jar, which was used to create the screenshots and is based on the above shown sample code, is available for download at the JModalWindow project .
I would like to mention the people whose ideas contributed to the creation of this project (in alphabetical order):
- Sandip Chitale, for his ideas on creating a modal window.
- Maks Smits, web designer at Quobell , for his suggestion to blur the blocked window.
- Dan Syrstad, for his idea on creating a busy frame.
I would like to thank the people whose support contributed to the existence of this project (in alphabetical order):
- Ise Douwes at Quobell , my former employer, for letting me have the copyright on the source code, based on, as he put it,
"intellectual ownership."
- Oleg N. Sukhodolsky at Sun, for reviewing the source code and for his suggestion for added functionality.
- Scott Violet at Sun, for noticing my email, among other things.
- Kathy Walrath at Sun, for editing this article.
Jene Jasper holds a degree in Mathematics and works as a Sun Java Certified Programmer for ABZ (an ADP Company).