Conquer Swing deficiencies in MDI development

Add more functionality to your Multiple Document Interface (MDI) applications

Developers have used the Multiple Document Interface (MDI) for many years. It provides an understandable interface for building applications that require multiple documents or windows to be hosted simultaneously within a parent application. In an MDI application, a parent application contains a main application frame that hosts a variety of child frames within it. Users can then move, resize, minimize, and maximize these child windows on the desktop as desired.

Creating an MDI application in Swing is relatively trivial. Swing provides two out-of-box components for creating MDI applications: JDesktopPane and JInternalFrame. You use JDesktopPane as the hosting frame while JInternalFrames act as the child frames that users move and resize.

On the surface, Swing seems to provide all the desired behavior for building an MDI application. However, creating an MDI application that behaves like users expect is quite challenging. For one reason or another, Swing's designers have omitted what would be considered standard MDI functionality on other platforms. The two most often requested features appear to be as follows:

  1. Making the desktop pane scrollable: As a user positions some or all of an internal frame outside the desktop pane's viewable area, scrollbars will magically appear. This is required so that a user doesn't accidentally "lose" a child frame by positioning it outside the desktop pane's viewable area.
  2. Having a windows menu available: A windows menu that lists all active child frames and lets users easily switch focus between the child frames is needed as well.

Scroll a desktop pane

The first issue we will tackle is allowing the desktop pane to automatically show scrollbars when a child frame is positioned outside its viewable area. We will create this desired behavior by extending the JDesktopPane class into our own MDIDesktopPane. The standard mechanism for adding scroll capability to a component is to first create the component and then add it to a scroll pane's viewport. One of our design goals should be to preserve this methodology of achieving scrolling in our MDIDesktopPane.

We are going to add scrolling to the desktop pane by simply increasing and decreasing the desktop pane's physical size as needed. Increasing the desktop pane's size beyond the size of the scroll pane's viewport will cause scrollbars to appear automatically. Of course, we have to make sure that the desktop pane isn't sized smaller than the scroll pane's viewport.

To accomplish this, we must be able to track the activity of child frames within the desktop pane. When the user finishes moving a child frame, we must determine if the child frame is now out of the desktop pane's bounds, and if so, increase the desktop pane's size so it encompasses the outer edges of the newly moved child frame. In a nutshell, every time the user adjusts the child frame's size or position, we must check the positioning of all child frames and adjust the desktop pane's size to ensure that it encompasses the outermost edges of the child frames.

The question then becomes how do we track the activities of the child frames. One approach might be to add listeners to every child frame that is added to the desktop pane; however, there is a better approach. The JDesktopPane manages user interaction with child frames through a DesktopManager. The DesktopManager is an interface defined by Swing whose methods are called when a frame is resized, closed, moved, maximized, or minimized.

While we can write a DesktopManager from scratch, a more convenient approach is to simply extend the existing DefaultDesktopManager class in Swing and add our code as needed. This results in the class MDIDesktopManager, which is automatically created by the MDIDesktopPane when constructed. The MDIDesktopManager then becomes the default desktop manager through the JDesktopPane's setDesktopManager() method as shown below.

    public MDIDesktopPane() {
        manager=new MDIDesktopManager(this);
        setDesktopManager(manager);
        //... 
    }

Within MDIDesktopManager we override the methods endResizingFrame() and endDraggingFrame(). Overriding these two methods lets us track child frame behavior. The overridden methods appear below:

    public void endResizingFrame(JComponent f) {
        super.endResizingFrame(f);
        resizeDesktop();
    }
    public void endDraggingFrame(JComponent f) {
        super.endDraggingFrame(f);
        resizeDesktop();
    }

In each of these methods we first call the super methods and then call a protected method, resizeDesktop(), which is introduced in the MDIDesktopManager class. The resizeDesktop() method determines the outermost point of all child frames and then resizes the desktop accordingly. If the outermost point is actually within the boundaries of the desktop pane's viewable area, it simply resizes the desktop to the scroll pane's viewable area.

We also have to override a few methods within the JDesktopPane to achieve a consistent scrolling behavior. First we override the setBounds() method. This is needed because the scrollable area will change if the desktop pane itself is made smaller or bigger:

    public void setBounds(int x, int y, int w, int h) {
        super.setBounds(x,y,w,h);
        checkDesktopSize();
    }

The checkDesktopSize() simply ensures that the desktop pane is visible and has a parent before calling the resizeDesktop() method in MDIDesktopManager. For similar reasons, we override the remove() method. If a child frame is removed, this can again impact the size of the scrollable region, so we need to check it:

    public void remove(Component c) {
        super.remove(c);
        checkDesktopSize();
    }

Finally, we add a new overloaded add method that specifically takes a JInternalFrame as a parameter. This is not absolutely necessary, but is done as a convenience mechanism so that newly added frames can be cascaded instead of appearing directly on top of each other. Also, it automatically shows the frame within the desktop pane.

Creating a windows menu

When creating an MDI application, the development of a windows menu tends to be repeated over and over again. This is a special menu to MDI applications that shows a list of open windows and enables the user to focus on a specific one. It also enables the user to access a variety of window management functions, such as cascading or tiling the windows.

The WindowMenu class provides this functionality for a single MDIDesktopPane, which is passed to the WindowMenu in its constructor. The WindowMenu then adds an internal menu listener to itself that enables it to recreate its menu structure as needed for MDIDesktopPane's current state.

When the user clicks the windows menu, the WindowMenu class automatically adds menu items for cascade and tile, and then adds a separator if any child frames are present on the desktop. Finally, a menu item for each child frame is added, with each menu item displaying the same icon and title as the child frame. Each frame is attached to each corresponding child menu so that when the user clicks a menu, he or she can easily focus on the correct frame.

You can obtain the source code for this article in Resources.

Conclusion

Creating full-featured MDI applications with Swing doesn't mean your users have to give up standard features they have come to expect from other platforms. You can use the classes created in this article in your own applications to provide the scrolling and window menu functionality that users depend on from a MDI application.

Gerald Nunn is the Director of Java Development for Workbrain, Inc. He manages the Java team and leads the development of the Workbrain application interface and tool set using Java tools and technologies.

Learn more about this topic