JModalWindow

The JModalWindow Project

JModalWindow

The JModalWindow project was created to support modal functionality, similar to JDialog, without blocking all frames.

Source code, binaries, API documentation and a demo are all available for download.

Since the java.net java.net and kenai.com kenai.com forges will be going dark on April 28, 2017 the issues are just listed below. By the way the new location for java.net is Java Community Space Java Community Space.

For the same reason the Source Code Repository will be relocated to jmodalwindow.git.

Analytics

openhub.net

The Black Duck Open Hub analytics about Commits, Languages and Estimated Cost of the old java.net java.net jmodalwindow project had the html count upped a bit when I started hosting the Development Production Line manual there also. ;-)

SUN Article

The JModalWindow Project JModalWindow Project

by Jene Jasper on 7 september 2004

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 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
Figure 1. 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
Figure 2. A line busy effect, custom stop cursor, and relative positioning of 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.

private JModalWindow createStatusWindow(Component returnFocusComponent, boolean relative, String text) {
      Window owner = SwingUtilities.windowForComponent(returnFocusComponent);
      final JModalWindow jmw = new JModalWindow(owner, returnFocusComponent);
      jmw.getContentPane().setBackground(bg);
      jmw.getContentPane().setForeground(fg);
      jmw.getContentPane().setLayout(new GridBagLayout());

      JButton jbClose = createJButton("Cancel");
      jbClose.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
           jmw.dispose();
        }
      });

      BorderLayout bl = new BorderLayout();
      jmw.getContentPane().setLayout(bl);

      jmw.getContentPane().add(createLabel(text), BorderLayout.CENTER);
      jmw.getContentPane().add(jbClose, BorderLayout.SOUTH);

      jmw.setSize(300, 75);

      if (relative) {
          jmw.relativeToOwnerChild(returnFocusComponent);
      } else {
          jmw.centerOfOwner();
  //      jmw.centerOfScreen();  //another positioning possibility
      }

      return jmw;
  }
          

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.2. 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
To set a title for the frame (JModalFrame only).
boolean modal
To change the default modal setting for the window.

Each ModalWindow constructor performs the following actions:

  1. 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.
  2. The optional returnFocus is stored.
  3. 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.
  4. The list with blockingWindows is initialized. This Vector is used to make it possible to have multiple modal child windows.
  5. 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.
  6. 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.

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:

  1. Retrieve the default or the custom busy cursor.
  2. If the ModalWindow is not already blocked, save the current cursor and set the cursor to the busy cursor.
  3. JModalFrame only: Save current resizable status if the frame is not already blocked, and disable the resizing of the frame.
  4. Enable the JBusyPanel glass pane.
  5. Add the calling child window to the list of blockingWindows.
  6. Force the glass pane to get the focus so that it consumes KeyEvents.
  7. 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:

  • JModalFrame only: A call to getDefaultCloseOperation always returns JFrame.DO_NOTHING_ON_CLOSE.
  • The following WindowEvents are handled differently by processWindowEvent:
    • JModalFrame only: WindowEvent.WINDOW_ICONIFIED is reversed by setting the state back to NORMAL (see the method checkIconifyAllowed). This behavior will be changed with the release of version 1.5. For more information, see the section Latest Developments and Ideas.
    • JModalFrame only: WindowEvent.WINDOW_ACTIVATED results in moving its child windows to the front and in restoring the state of its child frames to NORMAL. See the method checkForBlockingWindows, which will be renamed to checkActivationAllowed in a later release.
    • WindowEvent.WINDOW_CLOSING is not allowed (see the method tryToDispose).

Unblocked by Child Window

When a ModalWindow is unblocked by a child window calling its setBusy method, the following actions occur:

  1. Remove the calling child window from the list of blockingWindows.
  2. Reset the cursor for the glass pane to the old cursor and disable the glass pane.
  3. Try to retrieve focus in the ModalWindow.
  4. JModalFrame only: Reset the resizable status to the stored wasResizable value.
  5. 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 (see getMissingImage below).
getStopImage
Creates a stop sign Stop Cursor 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.

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.

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.

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.

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. Check the JModalWindow downloads. 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.

A runnable .jar, which was used to create the screenshots and is based on the above shown sample code, is also available for download.

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 Developer for ABZ a Solera company.

ABZ Solera

Issues

# Description Fixed
Initial JDK 1.3 version. 1.0
Initial JDK 1.4 version. 1.2
2 Allow iconify of blocked frames when child modal frame is iconified. 1.3
3 When a JModalFrame is deiconified because it is blocked the frame isn't repainted fully. 1.3
4 Add the following method to the InputBlocker: addAdditionalModalToWindow(Window window), to make it possible to block more than one window. 1.4
5 Create JModalDialog that behaves in the same way as the JModalFrame. 1.4
1 Flickering JModalFrame when setBusy. 1.5
6 The library does not work properly in the second monitor in a dual monitor configuration. When you click the 'Open modal Window' button in the 'Test Modal Main Frame' (placed in the second monitor) the modal window does not appear relative to the button but in the first monitor. 1.5
7 New implementation for Utils.relativeToOwnerChild(). 1.5
9 Make blocked JModalWindow behave like JModalFrame. Because there is no proper event when the JModalWindow is activated via ALT+TAB only the mouse is supported. 1.5
10 When unblocked not only move toFront but deiconify. 1.5
11 Allow resizing and moving of blocked windows. 1.5
12 Change wait_for_close legacy c-style methodname. 1.5
8 Iconify support on InputBlocker interface. 1.5
14 Enable configuration of settings (see also FAQ 1). 1.6
15 Create a modal window close notify mechanism. 1.7
16 Reactivation mechanism for blocking window must display parent. 1.7
17 Blurring when resizing a JInternalFrame. 2.1
18 Add modal internal frames. 2.1
19 waitForClose on event dispatch thread (see also FAQ 2). 2.1
20 Blurring based on alpha channel. 2.2
21 Optionally block iconify for a JModalInternalFrame. 2.3
22 JModalFrame can close when a JModalInternalFrame is blocked. 2.3
13 Remove obselete code. Invalid
23 KeyboardFocusManager OOM Invalid

Alternatives

  • If you just need a window-specific modal frame Santhosh Kumar's Weblog might provide a less intrusive alternative.

  • Or take a look at the New Modality API in Mustang.

FAQ 1

I can't change the STOP-icon! I've tried:

UIManager.put("swingx.busy.cursor", "/home/images/custom-cursor.gif");2)

but then the STOP-icon is only changed to a smaller red square with a white X in it, which is not my icon…

How can I fix this?

The white cross on the red background is generated with Utils.getMissingImage() to indicate that the specified icon/image couldn't be found.

To make the image available on the classpath there are two options:

  1. Place the image busy_cursor.gif in for example a subdirectory …/classes/images and add the following line to your code:

    UIManager.put("swingx.busy.cursor", "images/busy_cursor.gif");

    Note: if the class files are packaged in a jar make sure the images are also packaged in that jar.

  2. The image cursor.gif is available in the directory /usr/images. Create a zip or jar file …/dist/lib/usr.zip containing the image and including the subfolders /usr/images. Then add the following line to your code:

    UIManager.put("swingx.busy.cursor", "usr/images/cursor.gif")

    And suppose the application is packaged in …/dist/swingx-1.4-demo.jar then run the application with the following command:

    java -classpath "swingx-1.4-demo.jar;lib/usr.zip" nl.jj.swingx.gui.modal.test.TestModalFrame

Note: In UIManager.put("swingx.busy.cursor", "/home/images/custom-cursor"); remove the first slash, because there is a difference between:

  • getInstance().getClass().getClassLoader().getResource(…); which I used and
  • getInstance().getClass().getResource(…);

in the way they retrieve the resource.

By the way: Due to a Win32 problem the cursor to must be 32 x 32. To create a 16 x 16 icon use for example the upper left 16 x 16 pixels and fill the rest with a transparent color.


2) Since enhancement 14 it is possible to use JModalConfiguration.setBusyCursor(java.awt.Cursor) as well.

FAQ 2

I'm having a problem using the waitForClose() method. Basically, when I use this method, the entire application freezes and won't respond to events or repaint itself.

This is due to the fact that wait() shouldn't be called on the event dispatch thread. Which means the waitForClose() is useless in ActionListeners and the like. For this reason waitForClose() now throws the following Error("Cannot call wait from the event dispatcher thread")3) when the method is called on the event dispath thread.

If you need to implement some action, when the window is closed, use the windowClosed method of for example the WindowAdapter and supply this to the addWindowListener method of the window, you want to monitor. For an example of its usage see: TestModalFrame method init() command jbNormalWindow.addActionListener(…);


3) Since enhancement 19 it is now also possible to simulate wait on the event dispatch thread (EDT). Just call the enableWaitOnEDT() method of the JModalConfiguration class to activate this.

Note: Because this method could throw a java.lang.SecurityException if a security manager exists and its SecurityManager.checkAwtEventQueueAccess() method denies access to the EventQueue, it isn't activated by default.