Create a scrollable virtual desktop in Swing

Enhance your Java GUIs with the JScrollableDesktopPane class

The JDesktopPane class, first introduced in JDK 1.2 as a subsidiary to Swing's GUI component series, lets you create a virtual desktop or Multiple Document Interface (MDI) in Java applications. JInternalFrame's various child windows or internal frames populate this desktop, and because those frames are internal, they are clipped at the boundary of the JDesktopPane container class (as opposed to JFrame's external frames, which are painted without regard to container boundaries).

This clipping, demonstrated in Figure 1, exemplifies one of JDesktopPane's inherent limitations: a user cannot view an internal frame's hidden portion without dragging the frame back within the virtual desktop boundary, or resizing the JDesktopPane container itself. Needless to say, such actions are not conducive to navigability and usability.

Figure 1. Internal frame clipped by the virtual desktop. Click on thumbnail to view full-size image.

JDesktopPane's second limitation is that it doesn't provide a simple method to switch between internal frames; instead, you must click upon the frame title bar. Should internal frames obscure one another, the user must drag each frame aside before the next one becomes accessible. This work becomes tedious if several internal frames overlap, as is possible in any MDI environment.

Introducing JScrollableDesktopPane

The JScrollableDesktopPane class presented in this article offers a solution to the aforementioned clipping and overlap problems, and mimics the interface of the original JDesktopPane class in order to easily upgrade your application. Figure 2 depicts the scrollable desktop pane in action. As Figure 2 shows, JScrollableDesktopPane involves three main subcomponents: a virtual desktop, a toolbar, and a menu.

Figure 2. The JScrollableDesktopPane class in action. Click on thumbnail to view full-size image.

The virtual desktop comprises the main display area. When internal frames are positioned outside the virtual desktop's boundary, scroll bars update to provide access to the cropped internal frames, solving the clipping problem.

A toolbar provides a lengthwise set of toggle buttons above the virtual desktop, with each button mapped to a corresponding internal frame. The toolbar contents automatically size to fit as you add or remove buttons. When you click a button, the associated frame centers upon the virtual desktop and positions atop all other internal frames, solving the accessibility problem.

You may register a menu bar with JScrollableDesktopPane so that your application can offer an alternative solution to the accessibility problem. Upon registration, a Window menu is added to the main application's menu bar. This menu contains Tile, Cascade, Auto, and Close options along with a set of radio buttons that serve as dynamic shortcuts to the internal frames. Tile saturates the desktop with a tiled version of all internal frames, as shown in Figure 3.

Figure 3. Internal frames displayed in Tile mode. Click on thumbnail to view full-size image.

Cascade positions each internal frame in diagonal succession, as shown in Figure 4.

Figure 4. Internal frames displayed in Cascade mode. Click on thumbnail to view full-size image.

Auto allows the user to automatically tile or cascade new internal frames (default is auto-cascade), and Close disposes of the active internal frame.

Implement JScrollableDesktopPane

As you read the implementation details that follow, keep the UML class diagram in Figure 5 handy. You can click on the image for the full implementation details.

Figure 5. UML class diagram of JScrollableDesktopPane at a conceptual level. Click on thumbnail to view the implementation-level UML class diagram.

The JScrollableDesktopPane class is a JPanel subclass built upon five major GUI components: the BaseDesktopPane, DesktopScrollPane, BaseInternalFrame, DesktopResizableToolbar, and DesktopMenu component classes. These GUI components are labeled in Figure 6.

Figure 6. JScrollableDesktopPane with major GUI components labeled. Click on thumbnail to view full-size image.

The BaseDesktopPane and DesktopScrollPane classes comprise the virtual desktop. BaseDesktopPane, a JDesktopPane subclass, is located within a container of DesktopScrollPane, a JScrollPane subclass. The JScrollPane Swing component provides a scrollable view replete with horizontal and vertical scroll bars. When you move or resize an internal frame within the BaseDesktopPane class, a ComponentListener event fires. This event updates the DesktopScrollPane class's scroll bars via manipulation of the BaseDesktopPane preferred size (set via the setPreferredSize() method). The dimensions of this preferred size are determined from the minimum and maximum extents of all internal frames upon the desktop:

   Rectangle viewP = getViewport().getViewRect();
   int maxX=viewP.width+viewP.x, maxY=viewP.height+viewP.y;
   int minX=viewP.x, minY=viewP.y;
   JInternalFrame f = null;
   JInternalFrame[] frames = getAllFrames();
   for (int i=0; i < frames.length; i++) {
      f = frames[i];
      if (f.getX() < minX) { // get min X
         minX = f.getX();
      }
      if ((f.getX() + f.getWidth()) > maxX) { // get max X
            maxX = f.getX() + f.getWidth();
      }
      if (f.getY() < minY) { // get min Y
         minY = f.getY();
      }
      if ((f.getY() + f.getHeight()) > maxY) { // get max Y
            maxY = f.getY() + f.getHeight();
      }
   }

This technique is similar to that employed by the ScrollDemo2 example found in The Java Tutorial.

Internal frames added to the virtual desktop are of class BaseInternalFrame, a JInternalFrame subclass. The BaseInternalFrame class provides the getter and setter methods necessary to fetch any associated toggle and menu buttons (i.e., get/setAssociatedMenuButton() and get/setAssociatedButton() methods). When you minimize an internal frame, a blank image replaces the icon, as you access minimized frames via the toolbar's toggle buttons and any icon images only clutter the desktop.

The DesktopResizableToolbar class comprises the toolbar in Figure 6. A ResizableToolBar (not depicted in Figures 5 and 6) subclass, DesktopResizableToolbar renders a generic toolbar whose contents automatically size to fit as you add or remove buttons. ResizeableToolBar itself subclasses JToolBar, a Swing component class that provides a container for toolbar buttons. Upon a button's addition or removal (or a toolbar resizing), the ResizeableToolBar class dynamically determines the width of the remaining buttons by dividing the total container width by the button number:

   int containerWidth = 
      getWidth() - getInsets().left - getInsets().right;
   int numButtons = getButtonCount(); 
   float buttonWidth = containerWidth / numButtons;

Each toggle button is of class BaseToggleButton, a subclass of Swing's JToggleButton component class. The BaseToggleButton class provides the necessary getter and setter methods to fetch the associated BaseInternalFrame class (i.e., get/setAssociatedFrame() methods).

DesktopMenu, a JMenu subclass, comprises the Window menu in Figure 6. Each dynamic menu shortcut (those numbered sequentially in Figure 6) is of class BaseRadioButtonMenuItem, a subclass of Swing's JRadioButtonMenuItem. Like BaseToggleButton, the BaseRadioButtonMenuItem class provides the necessary getter and setter methods to fetch the associated BaseInternalFrame class (i.e., get/setAssociatedFrame() methods). When an internal frame is destroyed, the DesktopMenu class renumbers the dynamic menu shortcuts to prevent any gaps in their sequence:

   Enumeration e = frameRadioButtonMenuItemGroup.getElements();
   int displayedCount = 1;
   int currentMenuCount = 0;
   BaseRadioButtonMenuItem b = null;
   while (e.hasMoreElements()) {
      b = (BaseRadioButtonMenuItem)e.nextElement();
      // compute the key mnemonic based upon the currentMenuCount
      currentMenuCount = displayedCount;
      // derive the mnemonic from the first digit only..
      if (currentMenuCount > 9) { 
         currentMenuCount/=10;  
      } 
      b.setMnemonic(KeyEvent.VK_0 + currentMenuCount);
      b.setText(displayedCount + 
         " " + b.getAssociatedFrame().getTitle());
      displayedCount++;
   }

Note that if an application does not register a menu bar with the scrollable desktop, then DesktopMenu is not created. As such, DesktopMenu is listed with a 0..1 cardinality in Figure 5's UML diagram.

I should mention some of the package's non-GUI classes. Figure 5's DesktopListener provides a shared event class for various system objects. It implements a ComponentListener interface for the virtual desktop and internal frames, as well as an ActionListener interface for the toggle and menu buttons. Action commands differentiate between the varying source buttons:

public void actionPerformed(ActionEvent e) {
   String actionCmd = e.getActionCommand();
   if (actionCmd.equals("Tile")) {
      desktopMediator.tileInternalFrames();
   }
   else if (actionCmd.equals("Cascade")) {
      desktopMediator.cascadeInternalFrames();
   }
   ...

Figure 5's DesktopMediator class coordinates state changes between other package objects per the Mediator design pattern. It reduces coupling between the coordinated classes (coupling being the number of objects a given class references and depends upon). For more information on these and other classes comprising JScrollableDesktopPane, refer to the source code and javadoc in Resources.

Note that JScrollableDesktopPane requires JDK 1.3 or greater to operate. If you use JDK 1.2, you must supplant the setDragMode() method in the BaseDesktopPane class with a call to the putClientProperty() method (see the comments in the BaseDesktopPane.java source code). Also, you must replace the getButtonCount() method in the ResizableToolBar class with a custom routine.

I tested JScrollableDesktopPane under Java 2 JDK 1.3.1-beta24 on Linux and JDK 1.3_02 on Windows and Intel Solaris.

Using JScrollableDesktopPane

To use JScrollableDesktopPane, you must import the com.tomtessier.scrollabledesktop.* package and ensure that you define the source code's scrollabledesktop.jar within the global or local classpath.

Since JScrollableDesktopPane is a standard subclass of Swing's JComponent class (JScrollableDesktopPane subclasses JPanel directly), you may add it to any suitable Swing container, including a JFrame container class.

Table 1 summarizes JScrollableDesktopPane's constructors.

Table 1. JScrollableDesktopPane's constructor summary

ConstructorAction
JScrollableDesktopPane()Creates the JScrollableDesktopPane object
JScrollableDesktopPane(JMenuBar mb)Creates the JScrollableDesktopPane object and registers a menu bar
JScrollableDesktopPane(JMenuBar mb, ImageIcon defaultFrameIcon)Creates the JScrollableDesktopPane object, registers a menu bar, and assigns a default internal frame icon

Table 2 summarizes JScrollableDesktopPane's methods.

Table 2. JScrollableDesktopPane's method summary

Return TypeMethodMethod action
JInternalFrameadd(JPanel frameContents)Adds an internal frame to the scrollable desktop with the provided contents
JInternalFrameadd(String title, JPanel frameContents)Adds an internal frame with the provided title and contents
JInternalFrameadd(String title, JPanel frameContents, boolean isClosable)Adds an internal frame with the provided title, contents, and closability setting
JInternalFrameadd(String title, ImageIcon icon, JPanel frameContents, boolean isClosable)Adds an internal frame with the provided title, contents, closability setting, and frame icon
JInternalFrameadd(String title, ImageIcon icon, JPanel frameContents, boolean isClosable, int x, int y)Adds an internal frame with the provided title, contents, closability setting, frame icon, and positioned at the given x and y coordinates
voidadd(BaseInternalFrame f)Adds an internal frame to the scrollable desktop
voidadd(BaseInternalFrame f, int x, int y)Adds an internal frame to the scrollable desktop, positioned at the given x and y coordinates
voidflagContentsChanged(BaseInternalFrame f)Flags the specified internal frame as "contents changed;" notifies the user when an inactive internal frame's contents have changed
JInternalFramegetSelectedFrame()Returns the internal frame currently selected upon the virtual desktop
voidsetSelectedFrame(BaseInternalFrame f)Selects the specified internal frame upon the virtual desktop
voidregisterDefaultFrameIcon(ImageIcon defaultFrameIcon)Registers a default icon for display in the title bars of internal frames
voidregisterMenuBar(JMenuBar mb)Registers a menu bar to which the Window menu may be applied

As Table 2 details, you add new internal frames to the virtual desktop via the add(JPanel frameContents) method. This method accepts a Swing JPanel class parameter containing the frame contents and returns a reference to the JInternalFrame class that we added to the scrollable desktop (in your own program, you may wish to save this reference for later use). The returned JInternalFrame is in fact a BaseInternalFrame class whose auxiliary methods you may access by casting, should the need arise.

Table 2 provides various add() method overloads so you can create the internal frame with a given title, icon, x/y position, and so forth. The two overloads add(BaseInternalFrame f) and add(BaseInternalFrame f, int x, int y) even allow an application to supply its own internal frame instance in mimicry of the original JDesktopPane class.

1 2 Page 1