9.5 Building a Desktop


In this section, we'll pull together some of the things we've discussed in the previous section to create an application using JDesktopPane, JInternalFrame, and a custom DesktopManager. The example will show:

  • 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 noniconified frame on the desktop

Figure 9-4 shows what the application looks like when it's running. 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" frames. No matter which frame is active or how the frames are arranged, the "Up" frame always appears 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 Java int, 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-4. SampleDesktop layered frames and background image
figs/swng2.0904.gif
Figure 9-5. SampleDesktop with tiled frames
figs/swng2.0905.gif

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 a JFrame that uses a JDesktopPane as its content pane. SampleDesktop has two inner classes. AddFrameAction is an Action used 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 of DefaultDesktopManager that 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.

9.5.1 Setting Things Up

The first thing to look at is the SampleDesktop constructor:

public SampleDesktop(String title) {     super(title);     setDefaultCloseOperation(EXIT_ON_CLOSE);     // Create a desktop and set it as the content pane. Don't set the layered     // pane, since it needs to hold the menu bar too.     desk = new JDesktopPane( );     setContentPane(desk);     // Install our custom desktop manager.     desk.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 called getContentPane( ).add(desk), but, as we discussed in Chapter 8, this just introduces an unnecessary extra level (the content pane would then be a JPanel holding only our JDesktopPane). The more important thing to avoid is calling setLayeredPane(desk). Remember, the layered pane is responsible for rendering the menu bar too. If you did this, the menu bar 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 menu bar. It uses instances of AddFrameAction for adding new frames (at "Up" and "Lo" levels), and it uses an instance of TileAction to 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 JLabel containing an image and adds this label to the lowest possible layer of the desktop. This ensures that nothing is ever 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.

9.5.2 Adding Frames to the Desktop

The AddFrameAction class is an Action we've added to the menu bar. When fired, AddFrameAction instantiates a JInternalFrame and adds it to the specified layer of the desktop. Here's the code for the actionPerformed( ) 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);   f.setVisible(true);  // Needed since 1.3 }

The important things to notice here are that we set the bounds, not just the size, of the new frame, and we explicitly make it visible. We get the name of the frame from the name of the action being handled. 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. Also, starting with SDK 1.3, internal frames start out invisible until you explicitly make them visible, just like regular frames.

9.5.3 Veto Power

In the previous code block, we added a VetoableChangeListener to each new frame we created. This listener is an instance of another inner class called IconPolice . 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 use JInternalFrame '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( )) {           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 PropertyVetoException from the vetoableChange( ) method, as we've done here.

9.5.3.1 Bounding the frames

The next class to look at is our custom desktop manager called SampleDesktopMgr. This class is an extension of DefaultDesktopManager, which overrides the default implementation of dragFrame( ). This is the method called any time the frame is moved. 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 is only moved to the edge of the desktop. The code for this method is included at the end of the chapter.

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).

9.5.4 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 the actionPerformed( ) 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( ) && f.isIcon( )) {       try {         f.setIcon(false);       }       catch (PropertyVetoException ex) {}     }     desk.getDesktopManager( ).resizeFrame(f, x, y, w, h);     x += w;   }   y += h;     // Start the next row.   x = 0; }

We call setIcon( ) on the frame rather than calling deiconifyFrame( ) on the DesktopManager. We do this because deiconifyFrame( ) does not actually change the state of the icon property 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 call setIcon(false).

Figure 9-6. setIcon( ) sequence diagram
figs/swng2.0906.gif

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 the frame is 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.

9.5.5 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);     setDefaultCloseOperation(EXIT_ON_CLOSE);     // Create a desktop and set it as the content pane. Don't set the layered     // pane, since it needs to hold the menu bar too.     desk = new JDesktopPane( );     setContentPane(desk);     // Install our custom desktop manager.     desk.setDesktopManager(new SampleDesktopMgr( ));     createMenuBar( );     loadBackgroundImage( );   }   // Create a menu bar 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" frame.     menu.add(new AddFrameAction(false)); // Add "lower" frame.     menu.add(new TileAction(desk));      // Add tiling capability.     setJMenuBar(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 adds 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);       f.setVisible(true);  // Needed since 1.3     }     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( )) {             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 {   // This is called whenever a frame is moved. This implementation keeps the frame   // from leaving the desktop.   public void dragFrame(JComponent f, int x, int y) {     if (f instanceof JInternalFrame) {  // Deal only with internal frames.       JInternalFrame frame = (JInternalFrame)f;       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?         x = 0;                  // Flush against the left side.       }       else {         if (x + frame.getWidth( ) > d.width) {     // Too far right?           x = d.width - frame.getWidth( );         // Flush against right side.         }       }       if (y < 0) {              // Too high?         y=0;                    // Flush against the top.       }       else {         if (y + frame.getHeight( ) > d.height) {   // Too low?           y = d.height - frame.getHeight( );       // Flush against the bottom.         }       }     }     // Pass along the (possibly cropped) values to the normal drag handler.     super.dragFrame(f, x, y);   } } //  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 with   public 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 size.     int 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 and location.     Dimension 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 and resizing each.     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( ) && f.isIcon( )) {           try {             f.setIcon(false);           }           catch (PropertyVetoException ignored) {}         }         desk.getDesktopManager( ).resizeFrame(f, x, y, w, h);         x += w;       }       y += h; // Start the next row.       x = 0;     }   } } 


Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net