Creating
MDI Apps
With Swing
With AWT, You Couldn't Do It;
with Swing, You Can
By Ethan Henry
Java Evangelist, KL Group
Ever
since the advent of Microsoft Windows 3.0 and the X Windows UNIX
environment, the MDI (Multiple Document Interface) model has been
the predominant format for organizing and presenting information
in windows-style environments. Open almost any currently available
Windows application (such as Word, Access, or Excel), and you'll
find that it's an MDI application. Many X Windows and Motif applications
are also MDI programs.
In a nutshell, MDI is a graphical user interface (GUI)
mechanism that makes it possible to arrange multiple child windows
inside a parent window. (An alternative user-interface model, called
SDI -- or single-document interface -- lets an application display
only one main window at a time).
In an MDI environment, child windows are totally contained
a their parent window – in effect, the parent window becomes a new
desktop within which the children operate. When you minimize a child
window, it is iconified at the bottom of the parent window. When
you move a child window, it is clipped by the boundaries of the
parent window. And when you minimize a parent window, only one entry
appears in the window list maintained by your window manager.
In pre-Swing versions of the Advanced Windowing Toolkit
(AWT), there was no way to build MDI applications. You could create
multiple Frame objects, but each Frame was a top-level window, and
was completely independent. You could create a MDI-like application
that created a Frame for each window, but then the user’s screen
quickly got cluttered -- and if the user wanted to minimize the
entire application to work on something else, the user had to minimize
each Frame. Forthermore, each individual Frame created an icon on
the desktop when it was minimized. Things got messy pretty quickly.
Swing eliminates all these problems by simply supporting
the MDI user interface. Finally, Swing allows Java developers to
create real MDI applications by providing a class named JInternalFrame.
In Swing, JInternalFrame objects are child windows held inside a
special container called a JDesktopPane. And a JDesktopPane
object can turn any container, like a JFrame or JApplet, into a
parent window.
Swing offers a solution
This article explains how Swing's JDesktopPane and
JDesktopPane classes work, and shows how you can use them to create
MDI-style applications using the Java programming language. Major
topics covered in this article are:
![Horizontal rule](../../shared_art/bar_purple_520.gif)
For
More Information . . .
If you aren't familiar with how container classes
work in Swing, it might be helpful to read the archived article
titled "Understanding
Containers" before you continue reading this article.
![Horizontal rule](../../shared_art/bar_purple_520.gif)
The
JInternalFrame class
JInternalFrame is extremely useful, but this usefulness
doesn’t come without a price – you can’t just drop a JInternalFrame
into any old container. You need a special container that understands
how to manage JInternalFrame objects – the JDesktopPane. So, in
order to use JInternalFrame, we have to know how to use JDesktopPane.
Before we can do that, however, we have to understand yet another
class – JLayeredPane. JDesktopPane derives from JLayeredPane and
most of the JDesktopPane functionality is inherited from it. So,
on to JLayeredPane.
How
JLayeredPane works
Architecturally, JLayeredPane is a container that
manages components in layers, so components can overlap and still
be managed and organized. Components in higher layers are displayed
above components in lower layers. Each layer is identified by an
Integer, but to make things easier to deal with, JLayeredPane uses
predefined constants to specific five specific layers, each one
above the preceding ones. These five predefined constants are:
- DEFAULT_LAYER: The bottommost layer, where most components
go.
- PALETTE_LAYER: Sits above the default layer. This layer is
useful for implementing components such as floating toolbars
and palettes. It makes them appear above other components.
- MODAL_LAYER: The layer used for model dialogs.
- POPUP_LAYER: The layer where popup windows appear. Putting
popup windows associated with combo boxes, tooltips and other
help text in this layer ensures that they appear above whatever
component generated them.
- DRAG_LAYER: When you want to drage a component, move it to
the drag layer. Then, while it is moving, it appears above all
other components. When you have finished dragging the component,
return the component to its previous layer.
Adding components
Although there are exactly five predefined layers,
there is plenty of space between these values -- enough space for
you to fine-tune exactly where the components in your program will
appear. To add components to a JLayeredPane, you use the add()method,
just as you would with any other container:
JFrame frame = new JFrame();
JLayeredPane layeredPane = frame.getLayeredPane();
JComponent child = [some component];
layeredPane.add(child);
Using the standard (one-parameter) version of the
add() method adds
a component to the default layer. It is equivalent to:
layeredPane.add(child, JLayeredPane.DEFAULT_LAYER);
Once you have placed a component inside a JLayeredPane,
you can change its position by calling setLayer():
layeredPane.setLayer(child, JLayeredPane.DRAG_LAYER);
// move the child around
layeredPane.setLayer(child, JLayeredPane.DEFAULT_LAYER);
Stacking components
You can stack components inside each layer of a layered
pane in the same way you would stack them in a standard AWT container
-- you just give each component a position number in the layer in
which it appears. The position numbers in a given layer range from
0 to 1 less than the number of components in the layer. The topmost
component has position 0, and a value of –1 indicates the bottommost
component. (You may notice that this arrangement is just the opposite
from the way that layers stack -- when you stack layers, the bottommost
component is at position 0). To set the position of a component
inside a layer when it is added, there is a third variant of add():
// add the component to the top of the default layer
layeredPane.add(child, JLayeredPane.DEFAULT_LAYER, 0);
To move components within the layer in which they
reside, you can use the convenience methods moveToFront()
and moveToBack()
on JLayeredPane, to move components up and down within their own
layer:
// move the component to the front
layeredPane.moveToFront(child);
// move it to the back
layeredPane.moveToBack(child);
Note that a call to moveToFront()
may not put the component above all other components in the JLayeredPane
- it only puts it in front of other components in the same layer
– components in higher layers will still appear in front of components
in lower layers. The same is true of moveToback().
One important thing to note about JLayeredPane is
that by default, it does not have a layout manager set and you shouldn’t
set one, as using a layout manager will prevent component from overlapping,
which is why you’re using JLayeredPane in the first place.
JLayeredPane objects are also used by JRootPane –
every root pane contains a layered pane. The root pane’s content
pane, which is the container that is most useful, is a JPanel that’s
been added to the layered pane below the default layer, so that
it’s automatically underneath anything else you add to the layered
pane.
Now that we’ve gone through all the work of explaining
how JLayeredPane works, the truth is that most developers will never
use it directly. If you’re dealing with JRootPane and you don’t
need to layer components or use JInternalFrame, you’ll probably
just skip the JLayeredPane and go directly to the root pane’s content
pane. If you do want to use internal frames, then you’re not going
to be using JInternalFrame, you’re going to be using JDesktopPane.
![Horizontal rule](../../shared_art/bar_purple_520.gif)
The
JDesktopPane class
JDesktopPane, a class derived from JLayeredPane,
does the same basic thing as JLayeredPane does – it manages overlapping
components. The most important difference between the two classes
is that JDesktopPane is specially designed to manage just one specific
type of component – JInternalFrame.
To do this job, JDesktopPane provides two extra methods
that can come in hand when you need to manage JInternalFrames. These
two methods are getAllFrames()
and getAllFramesInLayer().
Here's how you use them:
JDesktopPane desktop = [some desktop pane]
JInternalFrame frames[];
frames = desktop.getAllFrames();
// or
frames = desktop.getAllFramesInLayer
(JDesktopPane.DEFAULT_LAYER);
JDesktopPane, unlike JLayeredPane, has a UI class
associated with it in the Swing's pluggable-look-and-feel (plaf)
package. Consequently, its appearance and behavior are subject to
change, depending on which look and feel you select. While most
of the PL&Fs supplied with Swing don’t change the appearance
of the desktop pane, the desktop pane does get one important thing
from its UI class: its DesktopManager.
![Horizontal rule](../../shared_art/bar_purple_520.gif)
The
DesktopManager
To be precise, DesktopManager is an interface, not
a class. Classes that implement DesktopManager provide a set of
methods that allow a desktop pane to handle the various kinds of
operations that can be performed on an internal frame -- operations
such as opening, closing, dragging, resizing, iconifying, minimizing
and maximizing. For example, if the desktop manager wants to show
a frame that's being dragged using only the frame’s outline instead
of dragging the entire frame, it might override these methods from
DesktopManager:
public void beginDraggingFrame(JComponent f);
public void dragFrame(JComponent f, int x, int y);
public void endDraggingFrame(JComponent f);
Some of Swing's look-and-feel packages (such as
the Windows PL&F design) implement their own DesktopManager
classes. And there is a DefaultDesktopManager class in the main
Swing package which will be used if no other DesktopManager is available.
If you want to override the default behavior of the
actions listed above on individual internal frames, you can register
listeners on the individual internal frame objects. To change
the behavior of all the frames on a desktop, it's much easier to
subclass one of the existing DesktopManager classes and use it instead
of using the JDesktopPane’s default desktop manager. For example:
public class MyDesktopManager
extends DefaultDesktopManager {
public void activateFrame(JInternalFrame f) {
System.out.println
("Frame "+f+" obtained the focus");
}
}
// inside some other class
public JDesktopPane createDesktop() {
JDesktopPane desktop = new JDesktopPane();
desktop.setDesktopManager(new MyDesktopManager());
Return desktop;
}
Using
JInternalFrame
In Swing, an internal frame is a lightweight object
that provides many of the features of a native frame, including
dragging, closing, becoming an icon, resizing, title display, and
support for a menu bar. Generally, you create an internal frame
by instantiating a JInternalFrame object and then adding it to a
JDesktopPane. Actions specific to the look and feel that is being
used are then delegated to the DesktopManager object maintained
by the JDesktopPane (as set by the UI).
Now that we’re familiar with all the classes that
interact with the JInternalFrameclass, we’re ready to create a sample
application that uses internal frames. JInternalFrame
is a sample program that illustrates:
-
Creating a custom DesktopManager class.
-
Using an anonymous inner class.
-
Creating a number of JInternalFrames in a JDesktopPane
in different layers.
Example program: JInternalFrame
import com.sun.java.swing.*;
import java.awt.BorderLayout;
public class InternalFrames extends JFrame {
JDesktopPane desktop;
DesktopManager manager;
public InternalFrames() {
super("Internal Frames Demo");
top = new JDesktopPane();
desktop.setOpaque(false);
getContentPane().add(desktop,BorderLayout.CENTER);
manager = new DefaultDesktopManager() {
public void activateFrame(JInternalFrame f) {
super.activateFrame(f);
System.out.println(f);
}
};
desktop.setDesktopManager(manager);
JInternalFrame internal;
JButton button;
internal = new JInternalFrame("Always Below",
true,false,true,true);
button = new JButton("Ok");
internal.getContentPane().add(button,BorderLayout.CENTER);
internal.setBounds(0,0,200,75);
desktop.add(internal,
new Integer(desktop.DEFAULT_LAYER.intValue()-1));
internal = new JInternalFrame("Default Layer #1",
true,false,true,true);
button = new JButton("Ok");
internal.getContentPane().add(button,BorderLayout.CENTER);
internal.setBounds(25,25,200,75);
desktop.add(internal,desktop.DEFAULT_LAYER);
internal = new JInternalFrame("Default Layer #2",
true,false,true,true);
button = new JButton("Ok");
internal.getContentPane().add(button,BorderLayout.CENTER);
internal.setBounds(50,50,200,75);
desktop.add(internal,desktop.DEFAULT_LAYER);
internal = new JInternalFrame("Always Above",
true,false,true,true);
button = new JButton("Ok");
internal.getContentPane().add(button,BorderLayout.CENTER);
internal.setBounds(75,75,200,75);
desktop.add(internal,
new Integer(desktop.DEFAULT_LAYER.intValue()+1));
setSize(300,300);
show();
}
public static void main(String args[]) {
new InternalFrames();
}
}
![Horizontal rule](../../shared_art/bar_purple_520.gif)
Conclusion
The JInternalPane class adds great new functionality to GUIs written
in Java with Swing. Although creating and using JInternalPane might
seem complicated at first glance, once you understand all the parts
involved, they’re as easy to use as they are elegant.
|