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.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 JScrollableDesktopPaneThe 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.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.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 JScrollableDesktopPaneAs 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.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 JScrollableDesktopPaneTo 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 summaryConstructorActionJScrollableDesktopPane()Creates the JScrollableDesktopPane objectJScrollableDesktopPane(JMenuBar mb)Creates the JScrollableDesktopPane object and registers a menu barJScrollableDesktopPane(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 summaryReturn TypeMethodMethod actionJInternalFrameadd(JPanel frameContents)Adds an internal frame to the scrollable desktop with the provided contentsJInternalFrameadd(String title, JPanel frameContents)Adds an internal frame with the provided title and contentsJInternalFrameadd(String title, JPanel frameContents, boolean isClosable)Adds an internal frame with the provided title, contents, and closability settingJInternalFrameadd(String title, ImageIcon icon, JPanel frameContents, boolean isClosable)Adds an internal frame with the provided title, contents, closability setting, and frame iconJInternalFrameadd(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 coordinatesvoidadd(BaseInternalFrame f)Adds an internal frame to the scrollable desktopvoidadd(BaseInternalFrame f, int x, int y)Adds an internal frame to the scrollable desktop, positioned at the given x and y coordinatesvoidflagContentsChanged(BaseInternalFrame f)Flags the specified internal frame as “contents changed;” notifies the user when an inactive internal frame’s contents have changedJInternalFramegetSelectedFrame()Returns the internal frame currently selected upon the virtual desktopvoidsetSelectedFrame(BaseInternalFrame f)Selects the specified internal frame upon the virtual desktopvoidregisterDefaultFrameIcon(ImageIcon defaultFrameIcon)Registers a default icon for display in the title bars of internal framesvoidregisterMenuBar(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. Table 2 shows you can register a menu bar for the scrollable desktop via the registerMenuBar(JMenuBar mb) method, which constructs the Window menu within the provided menu bar. The registerDefaultFrameIcon(ImageIcon defaultFrameIcon) method registers a default icon for display in the new internal frame title bars upon the desktop. But you can also register a menu bar and default frame icon via Table 1’s overloaded constructor JScrollableDesktopPane(JMenuBar mb, ImageIcon defaultFrameIcon).Table 2’s getSelectedFrame() and setSelectedFrame(BaseInternalFrame f) methods offer access to the currently selected frame upon the virtual desktop. The flagContentsChanged(BaseInternalFrame f) method flags the specified internal frame as contents change, which simply colors the text of the associated toggle button red. This alteration gives the user a visual cue that an inactive frame’s contents have changed, inactive meaning the frame is either minimized or not selected upon the desktop. This method is useful if you need an unobtrusive means of notifying the user of inactive frame updates. Once you activate the frame, the toggle button’s color returns to normal.JScrollableDesktopPane exampleListing 1 presents a simple example of the JScrollableDesktopPane class: Listing 1. JScrollableDesktopPane exampleimport com.tomtessier.scrollabledesktop.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Listing1 { public Listing1() { // prepare the JFrame JFrame f = new JFrame("Scrollable Desktop Example"); f.setSize(300,300); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // prepare the menuBar JMenuBar menuBar = new JMenuBar(); f.setJMenuBar(menuBar); // create the scrollable desktop instance and add it to the JFrame JScrollableDesktopPane scrollableDesktop = new JScrollableDesktopPane(menuBar); // add the scrollable desktop to the JFrame f.getContentPane().add(scrollableDesktop); f.setVisible(true); // add internal frames to the scrollable desktop for (int i=0; i < 3; i++) { JPanel frameContents = new JPanel(); frameContents.add( new JLabel( "Internal frame " + i + " of JScrollableDesktopPane")); scrollableDesktop.add(frameContents); } } public static void main(String[] arg) { new Listing1(); } } In Listing 1, we create the scrollable desktop instance and register the menu bar in one step: JScrollableDesktopPane scrollableDesktop = new JScrollableDesktopPane(menuBar); We then add the scrollable desktop to the content pane of Swing’s JFrame class instance: f.getContentPane().add(scrollableDesktop); f.setVisible(true); Next, we create JPanel contents to populate the desktop. We prepare these JPanel instances in the manner typical to Swing containers, wherein we add components of the JComponent subclass. In this case, a JLabel component class provides those contents: JPanel frameContents = new JPanel(); frameContents.add(new JLabel( "Internal frame " + i + " of JScrollableDesktopPane")); When the contents are ready, we simply add the newly created JPanel instance to the scrollable desktop instance: scrollableDesktop.add(frameContents); When the above line executes, an internal frame with the specified contents is created upon the virtual desktop and a reference to the new frame is returned. Note that the JPanel instance you add to the scrollable desktop must be unique; if the instance previously exists, the internal frame that currently contains it will become blank.Upon executing Listing 1, we create the scrollable desktop shown in Figure 7. Try dragging the internal frames outside the boundary of the virtual desktop; the bottom and right scroll bars should update accordingly. Also experiment with the toggle and menu buttons; when clicked, these buttons should highlight the appropriate internal frame. You can also populate the virtual desktop by constructing the internal frames manually and then feed each instance directly to the scrollable desktop via the add(BaseInternalFrame f) method: JInternalFrame iFrame = new BaseInternalFrame(); iFrame.getContentPane().add(frameContents); iFrame.pack(); iFrame.setVisible(true); scrollableDesktop.add(iFrame); This technique equals the functionality of the original JDesktopPane‘s add(JInternalFrame f) method, except that each internal frame must be of the base class BaseInternalFrame. Using the above methodology, you can easily migrate to the JScrollableDesktopPane class from programs written for the JDesktopPane class.For a more thorough demonstration of the JScrollableDesktopPane class, refer to the TestDesktop example provided in the source code.Rock and scrollThe JScrollableDesktopPane class overcomes many limitations of its forebear, the JDesktopPane class. With the ability to scroll the virtual desktop over clipped areas and the power to instantly jump between internal frames, it provides an easy-to-use interface instantly recognizable among those familiar with windowing applications. Not all user interfaces require a virtual desktop of course, but those developers currently employing the JDesktopPane class or planning a Java-based MDI environment should consider this class.I have used the JScrollableDesktopPane class successfully in many of my own applications (see Resources). I welcome any feedback regarding your own experiences with the class, either positive or negative, as well as comments pertaining to the design and layout.In the meantime, happy scrolling!Tom Tessier is a consultant currently living in Edmonton, Alberta. He holds several industry certifications, including Sun Certified Java Developer and Sun Certified Web Component Developer for J2EE. JavaSoftware Development