|
|
|
|
Java SwingBy Robert Eckstein, Marc Loy & Dave Wood1st Edition September 1998 1-56592-455-X, Order Number: 455X 1252 pages, $49.95 |
Sample Chapter 9: Internal Frames
In this chapter:
Managing a Desktop
The JInternalFrame Class
The JInternalFrame.-JDesktopIcon Class
The InternalFrame-Event Class
The InternalFrame-Adapter Class
The JDesktopPane Class
The DefaultDesktop-Manager Class
Building a Desktop
Managing a Desktop
Certain GUI applications need to simulate a desktop environment by allowing multiple "frames" to be displayed within a single root window. These frames look like the normal frames you'd see on a real desktop, but are not actually known to the window manager, because they are not really windows in the normal sense of the term. For some types of applications (word processors, IDEs, etc.), this can be a very powerful approach to UI design.
In this chapter, we'll look at a collection of classes Swing provides to allow you to create this type of application in Java. At the end of the chapter, we'll provide a large sample program that shows how to implement a variety of useful features.
Overview
Before looking at each of the classes involved in the Swing desktop/internal frame model, we'll take a moment for an overview of how they all work together. Figure 9-1 shows the relationships between the classes we'll be covering in this chapter.
Figure 9-1. Internal Frame and Desktop class diagram ![]()
JInternalFrameis a container that looks much like aJFrame. The key difference is that internal frames can only exist within some other Java container.JInternalFrameimplements the following three interfaces:Accessible,WindowConstants,RootPaneContainer.Each internal frame keeps a reference to an instance of the static inner class called
JDesktopIcon. Like real frames,JInternalFrames can be iconified.JDesktopIconis the class responsible for taking the place of the frame when it gets iconified.Though it is not required,
JInternalFrames are typically used inside of aJDesktopPane.JDesktopPaneis an extension ofJLayeredPanethat adds direct support for managing a collection ofJInternalFrames in layers.JDesktopPaneuses an object called aDesktopManagerto control how different behavior, like iconification or maximization, is carried out. A default implementation of this interface,DefaultDesktopManager, is provided. We'll see how all of this functionality is broken out as we cover the various classes and interfaces involved.One more thing to notice about Figure 9-1 is that
JInternalFramesupports a new type of listener calledInternalFrameListener. This interface contains methods that match those defined by the AWTWindowListenerclass, but have slightly different names and takeInternalFrameEvents, rather thanWindowEvents, as input.The JInternalFrame Class
JInternalFrameis a powerful addition to Java, providing the ability to create lightweight frames that exist inside other components. An internal frame is managed entirely within some other Java container, just like any other component, allowing the program complete control over iconification, maximization, resizing, etc. Despite looking like "real" windows, the underlying windowing system knows nothing of the existence of internal frames.[1] Figure 9-2 shows what an internal frame looks like in the different look-and-feels.[2]
Figure 9-2. JInternalFrames in the three look-and-feels ![]()
There's quite a lot to discuss about
JInternalFrames, but most of their power comes when they are used inside aJDesktopPane. In this section, we will give a quick overview of the properties, constructors, and methods available inJInternalFrame, but we'll leave the more detailed discussion of using internal frames to the sections that follow.Properties
JInternalFramedefines the properties and default values shown in Table 9-1. Thebackgroundandforegroundproperties are delegated to the frame's content pane.
Table 9-1 JInternalFrame Properties
Property
Data Type
get
is
set
bound
Default Value
UI
InternalFrameUI
X
X
X
from L&F
UIClassID*
String
X
"InternalFrameUI"
accessibleContext*
AccessibleContext
X
JInternalFrame.
AccessibleJInternalFrame()background*
Color
X
X
from
contentPane()closable
boolean
X
X
false
closeda
boolean
X
X
X
false
contentPane*
Container
X
X
X
from
rootPanedefaultCloseOperation
int
X
X
HIDE_ON_CLOSE
desktopIcon
JInternalFrame.JDesktopIcon
X
X
JInternalFrame.
JDesktopIcon()desktopPane
JDesktopPane
X
null
foreground*
Color
X
X
from
contentPane()frameIcon
Icon
X
X
X
null
glassPane*
Component
X
X
X
from
rootPane()icona
boolean
X
X
X
false
iconifiable
boolean
X
X
false
JMenuBar*b
JMenuBar
X
X
X
from
rootPane()layer
int
X
0
layeredPane*
JLayeredPane
X
X
X
from
rootPane()maximizable
boolean
X
X
false
maximuma
boolean
X
X
X
false
resizable
boolean
X
X
false
rootPane*
JRootPane
X
X
JRootPane()
selecteda
boolean
X
X
X
false
title
String
X
X
X
""
warningString
String
X
null
See also properties from the JComponent class (Table 3-5). a. These properties are the only constrained properties in Swing. The set() methods for each of them fire PropertyChangeEvents to any registered VetoablePropertyChangeListeners. Consequently, calls to these set() methods must be wrapped in a try-catch block, checking for PropertyVetoException. See the example at the end of the chapter for more information.
b. This property replaces the deprecated
menuBarparperty.Three pairs of properties indicate whether or not something can be done to a frame and whether or not that thing is currently done to the frame. They are:
closable/closed,iconifiable/icon, andmaximizable/maximum. Note thatclosed,icon, andmaximumare constrained properties.The
contentPane,glassPane,layeredPane, andJMenuBarproperties come from theRootPaneContainerinterface and are taken directly from the frame'sJRootPane. TherootPaneproperty is set to a newJRootPanewhen the frame is constructed.The value of the
defaultCloseOperationproperty defaults toWindowConstants.HIDE_ON_CLOSE. This implies that when the frame is closed, itssetClosed()method will be called. The frame could be reopened at a later time.The
desktopIconreflects how the frame will be displayed when iconified. AJDesktopIcon(which leaves the rendering to the L&F) is created for the frame when it is instantiated. ThedesktopPaneproperty provides a convenient way to access theJDesktopPanecontaining the frame, if there is one.
frameIconis the icon painted inside the frame's titlebar (usually on the far left). By default, there is no icon. However, the basic look-and-feel checks to see if aframeIconhas been set and, if not, paints the "java cup" icon. This explains why an icon appears in the Windows L&F frame shown in Figure 9-2, but not in the others (which provide their ownpaint()implementations, rather than using the one provided by the basic L&F).[3]The
layerproperty indicates the frame's current layer, if it has been placed in aJLayeredPane. Theresizableproperty indicates whether the frame can be resized by dragging its edges or corners, andselectedindicates whether the frame has been selected (this typically determines the color of the titlebar).selectedis a constrained property.titlecontains the string for the titlebar.The
UIproperty holds the current L&F implementation for the frame, andUIClassIDreflects the class ID for internal frames.Finally, the
warningStringproperty, which is alwaysnull, is used to specify the string that should appear in contexts where the frame might be insecure. This is the technique used byjava.awt.Windowto display a string like "Warning: Applet Window" when a Java window is displayed from an applet. SinceJInternalFrames are always fully enclosed by some other top-level container, this property is alwaysnull.Events
JInternalFramefires anInternalFrameEvent(discussed later in this chapter) whenever the frame's state changes.The following standard methods are provided for working with events.
public synchronized void addInternalFrameListener(InternalFrameListener l)
public synchronized void removeInternalFrameListener(InternalFrameListener l)
protected void fireInternalFrameEvent(int id)Fire an event to registered internal frame listeners. The inputidmust be one of the valid constants defined inInternalFrameEvent.Like all the other Swing classes,
JInternalFramefiresPropertyChangeEvents when the value of any bound property is changed.JInternalFrameis unique in that it is the only Swing class that uses vetoable changes for some properties (closed,icon,maximum, andselected).Constants
Table 9-2 shows the constants defined in this class. They are all strings and contain the names of the bound properties.
Table 9-2 JInternalFrame Constants
Constant
Property
CONTENT_PANE_PROPERTY
Indicates that the content pane has changed
FRAME_ICON_PROPERTY
indidcaes that the frame's icon has changed
GLASS_PANE_PROPERTY
Indicates that the glass pane has changed
IS_CLOSED_PROPERTY
Indicates that the frame has been opened or closed
IS_ICON_PROPERTY
Indicates that the frame as been iconified or deiconified
IS_MAXIMUM_PROPERTYIndicates that the frame has been maximized or minimized
IS_SELECTED_PROPERTY
Indicates that the frame has been selected or deselected
LAYERED_PANE_PROPERTY
Indicates that the layered pane has changed
MENU_BAR_PROPERTY
Indicates that the menubar has changed
ROOT_PANE_PROPERTY
Indicates that the root pane has changed
TITLE_PROPERTY
Indicates that the frame's title has changed
Protected Fields
protected boolean closable
protected JInternalFrame.JDesktopIcon desktopIcon
protected Icon frameIcon
protected boolean iconable
protected boolean isClosed
protected boolean isIcon
protected boolean isMaximum
protected boolean isSelected
protected boolean maximizable
protected boolean resizable
protected JRootPane rootPane
protected String titleThese fields hold the values of many of the properties listed in Table 9-1. Subclasses should access them through the accessor methods, rather than using these fields directly.protected boolean rootPaneCheckingEnabled
Indicates whether the frame throws anErrorif an attempt is made to add components directly to the frame (rather than to its content pane). By default, this is set totrueonce the frame has been built. Subclasses could change this property if necessary.Constructors
JInternalFrameprovides constructors that allow several of its boolean properties to be set at creation time. By default,resizable,closable,maximizable, andiconifiableare all set tofalse.public JInternalFrame()
public JInternalFrame(String title)Create a new frame with all four properties set to false.public JInternalFrame(String title, boolean resizable)
public JInternalFrame(String title, boolean resizable, boolean closable)
public JInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable)
public JInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable)Allow one to four of the frame's boolean properties to be set at creation time.JLayeredPane Methods
These methods are applicable only if the frame is contained by a
JLayeredPane(otherwise, they do nothing).public void moveToBack()
public void toBack()Call the containing layered pane'smoveToBack()method, causing the frame to be the first (bottom) component painted in its layer.public void moveToFront()
public void toFront()Call the containing layered pane'smoveToFront()method, causing the frame to be the last (top) component painted in its layer.Miscellaneous Public Methods
public void dispose()
Makes the frame invisible, unselected, and closed.public void pack()
Works likeFrame'spack()method, causing the frame to be resized according to the preferred size of its components.public void reshape(int x, int y, int width, int height)
Calls its superclass implementation and then forces a repaint of the frame, so that decorations such as the title bar will be painted.public void show()
Makes the frame visible and selects it, bringing it to the front of its layer.public void updateUI()
Called to indicate that the L&F for the frame has changed.Protected Methods
protected void addImpl(Component comp, Object constraints, int index)
Called by the variousadd()methods. IfrootPaneCheckingEnabledis set totrue, this method throws anError, indicating that components should be added to the frame's content pane, not to the frame itself.protected JRootPane createRootPane()
Used by the constructor to create a newJRootPane.protected boolean isRootPaneCheckingEnabled()
Indicates the current value ofrootPaneCheckingEnabled.protected void setRootPane( JRootPane root)
Called by the constructor to set the frame's root pane.Use of Glass Pane
JInternalFrameis the only Swing class that makes use of the glass pane (see Chapter 8 for a general discussion of the glass pane). To be precise,JInternalFrameitself doesn't do anything special with the glass pane, but the default UI implementation (BasicInternalFrameUI) does. This class toggles the visibility of an internal frame's glass pane each time the state of the frame'sselectedproperty changes. When the frame is selected, the glass pane is made invisible, allowing components inside the frame to be accessed with the mouse. But when the frame is not selected, the glass pane is made visible. This means that the first time you click anywhere within a non-selected internal frame, the mouse click will not get through to the component within the frame that you clicked on, but will instead be intercepted by the glass pane, causing the frame to be selected (and causing the glass pane to be removed).The Metal L&F JInternalFrame.isPalette Client Property
If you plan to use the
Metal L&F in your application, you can take advantage of a special custom property supported byMetalInternalFrameUI. This client property allows you to define an internal frame as a palette. This effectively amounts to removing the thick border from the frame. This is a technique commonly used in word processing or graphics editing programs to provide small windows that contain a set of convenient edit buttons. If you couple the use of this client property with the use of the desktop's PALETTE_LAYER (discussed later), you'll have a nice borderless frame that will float above your other internal frames. Here's an idea of how you'd use this property:JInternalFrame palette = new JInternalFrame(); // use any constructor you wantpalette.putClientProperty("JInternalFrame.isPalette", Boolean.TRUE);palette.setBounds(0, 0, 50, 150);JDesktopPane desk = new JDesktopPane();desk.add(palette, JDesktopPane.PALETTE_LAYER);Other L&Fs will quietly ignore this property.
The JInternalFrame.JDesktopIcon Class
JDesktopIconis a static inner class ofJInternalFrame, used to provide an iconified view of a frame.JInternalFrameinstantiates aJDesktopIconwhen the frame is created. The class extendsJComponentand, like other Swing components, leaves all details of its visual appearance to its UI delegate.Note that this class has no relation at all to the Swing
Iconinterface.NOTE: You should not work with theJDesktopIconclass directly--the javadoc for this inner class indicates that it will go away in a future Swing release. We are including this brief description of the class for completeness until the change is made.Properties
JDesktopIcondefines the properties shown in Table 9-3. ThedesktopPaneproperty simply provides convenient, direct access to theJDesktopPanecontaining the icon, if applicable. This property comes directly from the icon's internal frame. TheinternalFrameproperty reflects the icon's tight coupling with theJInternalFrameclass. The icon's frame is set when the icon is constructed and typically should not be changed. As usual, theUIproperty provides access to the object's UI implementation, andUIClassIDis set to the expected value.
Table 9-3 JDesktopIcon Properties
Property
Data Type
get
is
set
bound
Default Value
UI
DesktopIconUI
X
X
from L&F
UIClassID*
String
X
"DesktopIconUI"
accessibleContext*
AccessibleContext
X
JDesktopIcon.AccessibleJDesktopIcon()
desktopPane
JDesktopPane
X
from internal frame
internalFrame
JInternalFrame
X
X
from constructor
See also properties from the JComponent class (Table 3-5). Constructors
public JDesktopIcon( JInternalFrame f )
Creates an icon for the specified internal frame.Methods
There is only one method other than the accessors for the icon's properties.
public void updateUI()
Indicates that the icon's L&F should be updated.The InternalFrameEvent Class
As we described earlier in the chapter,
JInternalFrames fireInternalFrameEvents when the state of the frame changes. This is a standardAWTEventsubclass, providing a number of constants to define the type of change that was made to the frame.Constants
Table 9-4 shows constants defined as possible values for the event ID.
Table 9-4 InternalFrameEvent Constants
Constant
Type
Description
INTERNAL_FRAME_ACTIVATED
int
The frame has been activated, typically causing the title bar to change to a special color and the frame to gain focus
INTERNAL_FRAME_CLOSED
int
The frame has been closed (sent any time the frame is closed)
INTERNAL_FRAME_CLOSING
int
The frame is about to be closed (sent when the user clicks the closebox on the frame)
INTERNAL_FRAME_DEACTIVATED
int
The frame has been deactivated, typically causing the title bar to change to a default color and the frame to lose focus
INTERNAL_FRAME_DEICONIFIED
int
The frame has been restored from an icon
INTERNAL_FRAME_ICONIFIED
int
The frame has been iconified
INTERNAL_FRAME_OPENED
int
The frame has been opened
INTERNAL_FRAME_FIRST
int
The first integer value used to represent the above event IDs
INTERNAL_FRAME_LAST
int
The last integer value used to represent the above event IDs
Constructor
public InternalFrameEvent( JInternalFrame source, int id)
Creates a new event. Theidshould be taken from the list of constants provided above.Method
public String paramString()
This method, used bytoString(), returns a parameter string describing the event.The InternalFrameListener Interface
JInternalFramefiresInternalFrameEvents to registeredInternalFrameListeners. This interface defines the following set of methods (which have a one-to-one correspondence to the methods in thejava.awt.event.WindowListenerinterface).Methods
All of these methods except
internalFrameClosing()are called by theJInternalFramewhen its properties are changed.public abstract void internalFrameActivated(InternalFrameEvent e)
The frame has been activated, typically meaning that it will gain focus and be brought to the front.public abstract void internalFrameClosed(InternalFrameEvent e)
The frame has been closed.public abstract void internalFrameClosing(InternalFrameEvent e)
The frame is closing. This is called by the L&F when the close button is clicked.public abstract void internalFrameDeactivated(InternalFrameEvent e)
The frame has been deactivated.public abstract void internalFrameDeiconified(InternalFrameEvent e)
The frame has been restored from an icon.public abstract void internalFrameIconified(InternalFrameEvent e)
The frame has been reduced to an icon.public abstract void internalFrameOpened(InternalFrameEvent e)
A previously closed frame has been opened.The InternalFrameAdapter Class
This class follows the standard AWT 1.1 listener/adapter pattern by providing empty implementations of the seven methods defined in the
InternalFrameListenerinterface. If you are only interested in certain types of events, you can create a subclass of this adapter that implements only the methods you care about.Methods
The following methods have empty implementations in this class:
public void internalFrameActivated(InternalFrameEvent e)
public void internalFrameClosed(InternalFrameEvent e)
public void internalFrameClosing(InternalFrameEvent e)
public void internalFrameDeactivated(InternalFrameEvent e)
public void internalFrameDeiconified(InternalFrameEvent e)
public void internalFrameIconified(InternalFrameEvent e)
public void internalFrameOpened(InternalFrameEvent e)The JDesktopPane Class
JDesktopPaneis an extension ofJLayeredPane, which uses aDesktopManagerto control the placement and movement of frames. Figure 9-3 shows whatJDesktopPanelooks like in the different look-and-feels. Like its superclass,JLayeredPanehas anulllayout manager. Components added to it must be placed at absolute locations with absolute sizes, because it is intended to be used to houseJInternalFrames, which rely on the user to determine their placement.
Figure 9-3. JDesktopPanes in the three look-and-feels
![]()
Another reason for using
JDesktopPaneis to allow popup dialog boxes to be displayed usingJInternalFrames. This is discussed in detail in the next chapter.Properties
Table 9-5 shows the properties defined by
JDesktopPane. TheallFramesproperty provides access to allJInternalFrames contained by the desktop. ThedesktopManagerproperty holds theDesktopManagerobject supplied by the pane's L&F. We'll cover the responsibilities of theDesktopManagerin the next section. Theopaqueproperty defaults totrueforJDesktopPanes andisOpaque()is overridden so that it always returnstrue.UIcontains theDesktopPaneUIimplementation, andUIClassIDcontains the class ID forJDesktopPane.
Table 9-5 JDesktopPane Properties
Property
Data Type
get
is
set
bound
Default Value
UI
DesktopPaneUIX
X
from L&F
UIClassID*
String
X
"DesktopPaneUI"
accessibleContext*
AccessibleContext
X
JDesktopPane.AccessibleJDesktopPane()
allFrames
JInternalFrame[]X
empty array
desktopManager
DesktopManagerX
X
from L&F
opaque*
boolean
X
X
true
See also properties from the JLayeredPane class (Table 8-4).
Constructor
public JDesktopPane()
Creates a new desktop and callsupdateUI(), resulting in the L&F implementation installing aDesktopManager.Methods
public JInternalFrame[] getAllFramesInLayer(int layer)
Returns all frames that have been added to the specified layer. This includes frames that have been iconified.public void updateUI()
Called to indicate that the L&F for the desktop should be set.The DesktopManager Interface
This interface is responsible for much of the management of internal frames contained by
JDesktopPanes. It allows a look-and-feel to define exactly how it wants to manage things such as frame activation, movement, and iconification. Most of the methods inInternalFrameUIimplementations should delegate to aDesktopManagerobject. As described earlier,DesktopManagers are contained byJDesktopPaneobjects and are intended to be set by the L&F.Methods
The majority of the methods in this interface act on a given
JInternalFrame. However, those methods that could be applied to other types of components do not restrict the parameter unnecessarily (they accept anyJComponent), despite the fact that they will typically only be used withJInternalFrames. The exception to this in the current Swing release is a single use ofsetBoundsForFrame(), (called fromBasicDesktopIconUI), which passes in aJDesktopIconrather than aJInternalFrame. If you implement your ownDesktopManageror other L&F classes, you may find a need for this flexibility.public abstract void activateFrame( JInternalFrame f )
Called to indicate that the specified frame should become active.public abstract void beginDraggingFrame( JComponent f )
Called to indicate that the specified frame is now being dragged. The given component will normally be aJInternalFrame.public abstract void beginResizingFrame( JComponent f, int direction)
Called to indicate that the specified frame is going to be resized. The direction comes fromSwingConstantsand must beNORTH,SOUTH,EAST,WEST,NORTH_EAST,NORTH_WEST,SOUTH_EAST, orSOUTH_WEST. The given component will normally be aJInternalFrame. When resizing is complete,endResizingFrame()will be called.public abstract void closeFrame( JInternalFrame f )
Called to indicate that the specified frame should be closed.public abstract void deactivateFrame( JInternalFrame f )
Called to indicate that the specified frame is no longer active.public abstract void deiconifyFrame( JInternalFrame f )
Called to indicate that the specified frame should no longer be iconified.public abstract void dragFrame( JComponent f, int newX, int newY)
Called to indicate that the specified frame should be moved from its current location to the newly specified coordinates. The given component will normally be aJInternalFrame.public abstract void endDraggingFrame( JComponent f )
Called to indicate that the specified frame is no longer being dragged. The given component will normally be aJInternalFrame.public abstract void endResizingFrame( JComponent f )
Called to indicate that the specified frame is no longer being resized. The given component will normally be aJInternalFrame.public abstract void iconifyFrame( JInternalFrame f )
Called to indicate that the specified frame should be iconified.public abstract void maximizeFrame( JInternalFrame f )
Called to indicate that the specified frame should be maximized.public abstract void minimizeFrame( JInternalFrame f )
Called to indicate that the specified frame should be minimized. Note that this is not the same as iconifying the frame. Typically, calling this method will cause the frame to return to its size and position from before it was maximized.public abstract void openFrame( JInternalFrame f )
Called to add a frame and display it at a reasonable location. This is not normally called, because frames are normally added directly to their parent.public abstract void resizeFrame( JComponent f, int newX, int newY, int newWidth,
int newHeight)Called to indicate that the specified frame has been resized. Note that resizing is still in progress (many calls to this method may be made while the frame is being resized) after this method completes. The given component will normally be aJInternalFrame.public abstract void setBoundsForFrame( JComponent f, int newX, int newY, int newWidth, int newHeight)
Called to set a new size and location for a frame. The given component will normally be aJInternalFrame.The DefaultDesktopManager Class
DefaultDesktopManageris a default implementation of theDesktopManagerinterface. It serves as the base class for the Windows and Motif L&Fs, while the Metal L&F uses it without modification. In this section, we'll give a brief explanation of how each of the methods in the interface is implemented by this class.Methods
public void activateFrame( JInternalFrame f )
CallssetSelected(false)on all otherJInternalFrames contained by the specified frame's parent that are in the same layer as the given frame. It then moves the given frame to the front of its layer, selecting it.public void closeFrame( JInternalFrame f )
Removes the given frame from its parent. It also removes the frame's icon (if displayed). It sets the frame's previous bounds tonull.public void deiconifyFrame( JInternalFrame f )
Removes the given frame's icon from its parent and adds the frame. It then tries to select the given frame.public void dragFrame( JComponent f, int newX, int newY)
CallssetBoundsForFrame()with the given location and current dimensions.public void iconifyFrame( JInternalFrame f )
Removes the given frame from its parent and adds the frame's desktop icon. Before adding the icon, it checks to see if it has ever been iconified. If not, it callsgetBoundsForIconOf()to set the icon's bounds. This is only done once for a given frame, ensuring that each time a frame is iconified, it returns to the same location on the desktop.public void maximizeFrame( JInternalFrame f )
Maximizes the given frame so that it fills its parent. It also saves the frame's previous bounds for use inminimizeFrame(). Once the frame has been maximized, it is also selected.This method can be called on an iconified frame, causing it to be deiconified and maximized.public void minimizeFrame( JInternalFrame f )
Sets the frame's bounds to its previous bounds. If there are no previous bounds (previous bounds are set by callingmaximizeFrame()), the frame is not resized.public void openFrame( JInternalFrame f )
Gets the desktop icon for the given frame. If the icon's parent is non-null, the icon is removed from the parent, and the frame is added. If its parent isnull, this method does nothing.public void resizeFrame( JComponent f, int newX, int newY, int newWidth, int newHeight)
CallssetBoundsForFrame()with the given location and dimensions.public void setBoundsForFrame( JComponent f, int newX, int newY, int newWidth,
int newHeight)Moves and resizes the given frame (usingsetBounds()) and validates the frame if the size was actually changed.public void beginDraggingFrame( JComponent f )
public void beginResizingFrame( JComponent f, int direction)
public void deactivateFrame( JInternalFrame f )
public void endDraggingFrame( JComponent f )
public void endResizingFrame( JComponent f )These methods have empty implementations in this class.Protected Methods
This default implementation provides several convenience methods, which it uses in the methods described above. The methods relate to desktop icon management and the management of a frame's previous size (when maximized). If you subclass
DefaultDesktopManager, these methods will probably be of use to you.The frame's previous bounds and an indication of whether or not it has ever been iconified are stored in client properties on the frame itself.[4] The property names used are
previousBounds(which holds aRectangle) andwasIconOnce(which holds aBoolean).protected Rectangle getBoundsForIconOf( JInternalFrame f )
Gets the bounds for the given frame's icon. The width and height are taken directly from the size of the icon. The icon's location will be the lower-left corner of the desktop. If an icon has already been placed in this corner, the icon is placed directly to the right, continuing until an unclaimed position along the bottom of the frame is found. If there is no space along the bottom, a new row of icons is started directly above the first row. Once a frame has been iconified, its icon's location is set and the icon will always return to the same spot (unless the icon is moved by the user).protected Rectangle getPreviousBounds( JInternalFrame f )
Returns the frame's previous bounds (set when the frame is maximized). These bounds are retrieved from the frame'spreviousBoundsclient property.protected void removeIconFor( JInternalFrame f )
Removes the given frame's icon from its parent and repaints the region under the icon.protected void setPreviousBounds( JInternalFrame f, Rectangle r)
Saves the previous bounds of a frame. This is done by saving the frame's previous bounds in the frame itself, using the client property,previousBounds. This is generally called bymaximizeFrame()with the data being used in a subsequentminimizeFrame()call.protected void setWasIcon( JInternalFrame f, Boolean value)
Called byiconifyFrame()to indicate whether or not the frame has, at some time, been iconified. This is done by saving the booleanvaluein the frame itself, using the client propertywasIconOnce. This is used to determine whether or not the icon's bounds have been defined.protected boolean wasIcon( JInternalFrame f )
Determines whether or not a frame has ever been iconified (if it has, bounds will already be defined for the icon). This is done by returning thewas-IconOnceclient property on the frame.Building a Desktop
In this section, we'll pull together some of the things we've discussed in the previous pages to create an application using
JDesktopPane,JInternalFrame, and a customDesktopManager. The example shows:
- The effect of adding frames to different layers of the desktop.
- How to display a background image ("wallpaper") on the desktop.
- How to keep frames from being moved outside of the desktop.
- How to deiconify, move, and resize internal frames by frame "tiling."
- How to take advantage of
JInternalFrame's constrained properties by requiring that there be at least one non-iconified frame on the desktop.Figure 9-4 shows what the application looks like when it's running.
Figure 9-4. SampleDesktop layered frames and background image
![]()
Here we see the desktop with three frames, plus a fourth that has been iconified. The frames titled "Lo" are in a lower layer than the "Up" frame. No matter which frame is active or how the frames are arranged, the "Up" frame will always appear on top of the others. Frames in the same layer can be brought to the front of that layer by clicking on the frame. This display also shows the use of a background image (what good is a desktop if you can't put your favorite image on the background, right?). This image is added to a very low layer (the lowest possible integer, actually) to ensure that it is always painted behind anything else in the desktop. Figure 9-5 shows the same display after the frames have been "tiled."
Figure 9-5. SampleDesktop with tiled frames
![]()
Now, let's take a look at some of the code used to create this example. There are three primary classes:
SampleDesktop
This is the main class, which we chose to create as aJFramethat uses aJDesktopPaneas its content pane.SampleDesktophas two inner classes.AddFrameActionis anActionused to add frames to the desktop. Recall from Chapter 3 that actions are a nice way to encapsulate functionality that you might want to invoke from multiple locations. The other inner class,IconPolice, is responsible for ensuring that if there is only a single frame on the desktop, it cannot be iconified.SampleDesktopMgr
An extension ofDefaultDesktopManagerthat keeps frames from being moved outside the bounds of the desktop.TileAction
A generic action class that can be used to tile all frames on a given desktop.Let's take a look at these classes piece by piece. The complete source listing is provided at the end of the chapter.
Setting Things Up
The first thing to look at is the
SampleDesktopconstructor:public SampleDesktop(String title) {super(title);addWindowListener(new BasicWindowMonitor());// Create a desktop and set it as the content pane. Don't set the layered// pane, since it needs to hold the menubar too.desk = new JDesktopPane();setContentPane(desk);// Install our custom desktop managerdesk.setDesktopManager(new SampleDesktopMgr());createMenuBar();loadBackgroundImage();}We set the frame's content pane to our new
JDesktopPane. Since we won't be adding anything else to the body of the frame, this is a good idea. We could also have calledgetContentPane().add(desk), but as we discussed in Chapter 8, this just introduces an unnecessary extra level (the content pane would then be aJPanelholding only ourJDesktopPane). The more important thing to avoid is callingsetLayeredPane(desk). Remember, the layered pane is responsible for rendering the menubar too. If you did this, the menubar would still be drawn at the top of the frame, but your desktop would be filling the same space, allowing frames to be placed over the menu.The
createMenuBar()method called here just adds a few options to the frame's menubar. It uses instances ofAddFrameActionfor adding new frames (at "Up" and "Lo" levels), and it uses an instance ofTileActionto support frame tiling. See the complete code listing at the end of this section for more details on this method.The
loadBackgroundImage()method looks like this:protected void loadBackgroundImage() {ImageIcon icon = new ImageIcon("images/matterhorn.gif");JLabel l = new JLabel(icon);l.setBounds(0, 0, icon.getIconWidth(), icon.getIconHeight());desk.add(l, new Integer(Integer.MIN_VALUE));}This method just creates a large
JLabelcontaining an image and adds this label to the lowest possible layer of the desktop. This ensures that nothing will ever be painted behind the background. In this example, we don't make any effort to resize or tile the background image, but it certainly could be done.Adding Frames to the Desktop
The
AddFrameActionclass is anActionwe've added to the menubar. When fired,AddFrameActioninstantiates aJInternalFrameand adds it to the specified layer of the desktop. Here's the code for theactionPerformed()method of this class:public void actionPerformed(ActionEvent ev) {JInternalFrame f = new JInternalFrame(name, true, true, true, true);f.addVetoableChangeListener(iconPolice);f.setBounds(0, 0, 120, 60);desk.add(f, layer);}The important thing to notice here is that we set the bounds, not just the size, of the new frame. If you don't specify a location (we've specified [0,0], the upper-left corner) for the frame, it won't appear on the desktop when you add it. Remember, there's no layout manager controlling the location of the components in a
JDesktopPane.Veto Power
In the previous code block, we added a
VetoableChangeListenerto each new frame we created. This listener is an instance of another inner class calledIconPolice. The purpose of this class is to ensure that the last frame on the desktop cannot be iconified. This may not be the most useful thing in the world to do, but it shows how to useJInternalFrame's constrained properties. Here's the code for this class.class IconPolice implements VetoableChangeListener {public void vetoableChange(PropertyChangeEvent ev)throws PropertyVetoException {String name = ev.getPropertyName();if (name.equals(JInternalFrame.IS_ICON_PROPERTY)&& (ev.getNewValue() == Boolean.TRUE)) {JInternalFrame[] frames = desk.getAllFrames();int count = frames.length;int nonicons = 0; // how many are not icons?for (int i=0; i<count; i++) {if (frames[i].isIcon() == false) {nonicons++;}}if (nonicons <= 1) {throw new PropertyVetoException("Invalid Iconification!", ev);}}}}If you haven't used constrained properties before, this code may look a little strange. The idea behind constrained properties is that before a property is changed, all registered listeners are given the opportunity to "veto" the change. This is done by throwing a
PropertyVetoExceptionfrom thevetoableChange()method as we've done here.Bounding the Frames
The next class to look at is our custom desktop manager called
SampleDesktopMgr. This class is an extension ofDefaultDesktopManagerwhich overrides the default implementation ofsetBoundsForComponent(). This is the method called any time the frame is moved or resized. The new implementation simply checks the new location of the frame to see if the requested change of bounds will result in part of the frame moving outside of the desktop. If so, it adjusts the coordinates so that the frame will only be moved to the edge of the desktop. The code for this method is included at the end of the chapter.In order to correctly handle invalid bounds changes, we need to know if the frame is being moved or if it is being resized. This will affect how we adjust the frame's size and location in the
setBoundsForComponent(). In this example, we do this by storing a client property[5] calledRESIZINGin each frame. InbeginResizingFrame(), we set this property totrue. WhenendResizingFrame()is called, we switch it tofalse. Here's how we do this:protected static final String RESIZING = "RESIZING";public void beginResizingFrame(JComponent f, int dir) {f.putClientProperty(RESIZING, Boolean.TRUE);}public void endResizingFrame(JComponent f) {f.putClientProperty(RESIZING, Boolean.FALSE);}This class is included only as a useful example of the type of thing you might want to do with a desktop manager. If you don't mind frames being moved off the desktop, you can always just use
DefaultDesktopManager(the default).Moving Things Around
The last class in this example is called
TileAction. Its job is to resize all of the frames and lay them out in a grid on a desktop. There are a few interesting things that take place in theactionPerformed()method of this class. First, we get all of the frames on the desktop and determine where each frame should be placed and how big it should be based on the size of the desktop and the total number of frames. For the details of how this is calculated, see the full code listing at the end of the chapter.Next, we iterate over all of the frames on the desktop, deiconifying any iconified frames and then setting the size and location of each frame. Here's the block of code that does this work:
for (int i=0; i<rows; i++) {for (int j=0; j<cols && ((i*cols)+j<count); j++) {JInternalFrame f = allframes[(i*cols)+j];if ((f.isClosed() == false) && (f.isIcon() == true)) {try {f.setIcon(false);}catch (PropertyVetoException ex) {}}desk.getDesktopManager().resizeFrame(f, x, y, w, h);x += w;}y += h; // start the next rowx = 0;}We call
setIcon()on the frame, rather than callingdeiconifyFrame()on theDesktopManager. We do this becausedeiconifyFrame()does not actually change the state of theiconproperty in the frame, which can result in unexpected behavior down the road. Figure 9-6 shows the sequence of calls (only certain significant calls are identified) made when we callsetIcon(false).
Figure 9-6. setIcon() sequence diagram
![]()
Note that the UI delegate is registered as a listener for property change events. When it hears that a frame is being deiconified, it calls
deiconifyFrame()on the desktop manager. This object then adds the frame to its container (the desktop pane in this case), removes the icon, and selects the newly added frame.Once we've got the frame deiconified, we relocate and resize it by calling the
resizeFrame()method on the desktop manager:desk.getDesktopManager().resizeFrame(f, x, y, w, h);We call this method (instead of just calling
setBounds()on the frame) because it validates the frame after setting its bounds.Source Code
Here's the complete source code (three files) for this example:
// SampleDesktop.java//import javax.swing.*;import java.awt.event.*;import java.awt.*;import java.util.*;import java.beans.*;// An example that shows how to do a few interesting things using// JInternalFrames, JDesktopPane, and DesktopManager.public class SampleDesktop extends JFrame {private JDesktopPane desk;private IconPolice iconPolice = new IconPolice();public SampleDesktop(String title) {super(title);addWindowListener(new BasicWindowMonitor());// Create a desktop and set it as the content pane. Don't set the layered// pane, since it needs to hold the menubar too.desk = new JDesktopPane();setContentPane(desk);// Install our custom desktop managerdesk.setDesktopManager(new SampleDesktopMgr());createMenuBar();loadBackgroundImage();}// Create a menubar to show off a few things.protected void createMenuBar() {JMenuBar mb = new JMenuBar();JMenu menu = new JMenu("Frames");menu.add(new AddFrameAction(true)); // add "upper" framemenu.add(new AddFrameAction(false)); // add "lower" framemenu.add(new TileAction(desk)); // add tiling capabilitysetJMenuBar(mb);mb.add(menu);}// Here we load a background image for our desktop.protected void loadBackgroundImage() {ImageIcon icon = new ImageIcon("images/matterhorn.gif");JLabel l = new JLabel(icon);l.setBounds(0,0,icon.getIconWidth(),icon.getIconHeight());// Place the image in the lowest possible layer so nothing// can ever be painted under it.desk.add(l, new Integer(Integer.MIN_VALUE));}// This class will add a new JInternalFrame when requested.class AddFrameAction extends AbstractAction {public AddFrameAction(boolean upper) {super(upper ? "Add Upper Frame" : "Add Lower Frame");if (upper) {this.layer = new Integer(2);this.name = "Up";}else {this.layer = new Integer(1);this.name = "Lo";}}public void actionPerformed(ActionEvent ev) {JInternalFrame f = new JInternalFrame(name,true,true,true,true);f.addVetoableChangeListener(iconPolice);f.setBounds(0, 0, 120, 60);desk.add(f, layer);}private Integer layer;private String name;}// A simple vetoable change listener that insists that there is always at// least one noniconified frame (just as an example of the vetoable// properties).class IconPolice implements VetoableChangeListener {public void vetoableChange(PropertyChangeEvent ev)throws PropertyVetoException {String name = ev.getPropertyName();if (name.equals(JInternalFrame.IS_ICON_PROPERTY)&& (ev.getNewValue() == Boolean.TRUE)) {JInternalFrame[] frames = desk.getAllFrames();int count = frames.length;int nonicons = 0; // how many are not icons?for (int i=0; i<count; i++) {if (frames[i].isIcon() == false) {nonicons++;}}if (nonicons <= 1) {throw new PropertyVetoException("Invalid Iconification!", ev);}}}}// A simple test program.public static void main(String[] args) {SampleDesktop td = new SampleDesktop("Sample Desktop");td.setSize(300, 220);td.setVisible(true);}}// SampleDesktopMgr.java//import javax.swing.*;import java.awt.event.*;import java.awt.*;import java.util.*;import java.beans.*;// A DesktopManager that keeps its frames inside the desktop.public class SampleDesktopMgr extends DefaultDesktopManager {// We'll tag internal frames that are being resized using a client// property with the name RESIZING. Used in setBoundsForFrame().protected static final String RESIZING = "RESIZING";public void beginResizingFrame(JComponent f, int dir) {f.putClientProperty(RESIZING, Boolean.TRUE);}public void endResizingFrame(JComponent f) {f.putClientProperty(RESIZING, Boolean.FALSE);}// This is called any time a frame is moved or resized. This// implementation keeps the frame from leaving the desktop.public void setBoundsForFrame(JComponent f, int x, int y, int w, int h) {if (f instanceof JInternalFrame == false) {super.setBoundsForFrame(f, x, y, w, h); // only deal w/internal frames}else {JInternalFrame frame = (JInternalFrame)f;// Figure out if we are being resized (otherwise it's just a move)boolean resizing = false;Object r = frame.getClientProperty(RESIZING);if (r != null && r instanceof Boolean) {resizing = ((Boolean)r).booleanValue();}JDesktopPane desk = frame.getDesktopPane();Dimension d = desk.getSize();// Nothing all that fancy below, just figuring out how to adjust// to keep the frame on the desktop.if (x < 0) { // too far left?if (resizing)w += x; // don't get wider!x=0; // flush against the left side}else {if (x+w>d.width) { // too far right?if (resizing)w = d.width-x; // don't get wider!elsex = d.width-w; // flush against the right side}}if (y < 0) { // too high?if (resizing)h += y; // don't get taller!y=0; // flush against the top}else {if (y+h > d.height) { // too low?if (resizing)h = d.height - y; // don't get taller!elsey = d.height-h; // flush against the bottom}}// Set 'em the way we like 'emsuper.setBoundsForFrame(f, x, y, w, h);}}}// TileAction.java
//import javax.swing.*;import java.awt.event.*;import java.awt.*;import java.beans.*;// An action that tiles all internal frames when requested.public class TileAction extends AbstractAction {private JDesktopPane desk; // the desktop to work withpublic TileAction(JDesktopPane desk) {super("Tile Frames");this.desk = desk;}public void actionPerformed(ActionEvent ev) {// How many frames do we have?JInternalFrame[] allframes = desk.getAllFrames();int count = allframes.length;if (count == 0) return;// Determine the necessary grid sizeint sqrt = (int)Math.sqrt(count);int rows = sqrt;int cols = sqrt;if (rows*cols < count) {cols++;if (rows*cols < count) {rows++;}}// Define some initial values for size & locationDimension size = desk.getSize();int w = size.width/cols;int h = size.height/rows;int x = 0;int y = 0;// Iterate over the frames, deiconifying any iconified frames and then// relocating & resizing eachfor (int i=0; i<rows; i++) {for (int j=0; j<cols && ((i*cols)+j<count); j++) {JInternalFrame f = allframes[(i*cols)+j];if ((f.isClosed() == false) && (f.isIcon() == true)) {
try {f.setIcon(false);}catch (PropertyVetoException ex) {}}desk.getDesktopManager().resizeFrame(f, x, y, w, h);x += w;}y += h; // start the next rowx = 0;}}}
1.Note that
JInternalFrameextendsJComponent, notJFrameorFrame, so this statement should seem logical.2.The appearance of an internal frame in the Metal look-and-feel has changed very slightly since these screen shots were taken.
3.The
BasicLookAndFeelis an abstract base class from which all the Swing L&Fs extend. For more information, see Chapter 26, Look & Feel.4.See Chapter 3, Swing Component Basics, for an explanation of
JComponent's client property feature.5.For an explanation of client properties, see Chapter 3.
© 2001, O'Reilly & Associates, Inc.