Building a GUI: Components, Containers, and Layout Managers

   

Building a GUI: Components , Containers, and Layout Managers

Chapter 14 introduced you to the two-phase GUI building approach. Although this approach was used with the AWT's windowing toolkit, it can also be used with Swing's windowing toolkit. As you learned, the first phase constructs a GUI's look, and is achieved by working with components, containers, and layout managers.

Exploring Components

Swing provides the JComponent class (located in the javax.swing package) as the superclass for all its lightweight component and container classes. This class offers the following services:

  • PLAF support

  • Accessibility support

  • ToolTip support

  • Improved keystroke handling

  • Double-buffered painting structure that supports borders

  • Component-specific property support

PLAF has already been discussed. Accessibility will be discussed in the next chapter. As for the other services, you will get an opportunity to learn about them as you work your way through this chapter.

Buttons

The JButton class (located in javax.swing package) represents a new kind of button component. Unlike AWT buttons, this button can be associated with an icon and a mnemonic ” a key stroke that, when pressed in conjunction with the Alt key, results in a button click.

A button can serve as the default button (a button that is clicked when either the Return or Enter key is pressed). Its text can be aligned horizontally or vertically. HTML tags can be used to render the appearance of the button's text (on platforms that offer such support). Finally, a ToolTip can be specified to provide a description of the button's purpose. To see what a pair of Swing buttons look like, check out Figure 16.6.

Figure 16.6. Swing's buttons can display icons, and horizontally or vertically aligned and HTML formatted captions.

graphics/16fig06.gif

Figure 16.6's buttons were created from the ButtonDemo application. The source code to this application is presented in Listing 16.1.

Listing 16.1 The ButtonDemo Application Source Code
 // ButtonDemo.java import javax.swing.*; import java.awt.*; import java.awt.event.*; class ButtonDemo extends JFrame implements ActionListener {    ButtonDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       JButton jb = new JButton ("Ok", new ImageIcon ("bullet.gif"));       jb.setHorizontalAlignment (SwingConstants.LEFT);       jb.setToolTipText ("Press Ok to terminate the program.");       jb.addActionListener (this);       jb.setMnemonic ('O');       getContentPane ().add (jb, BorderLayout.CENTER);       jb = new JButton ("<html><i>Cancel</i></html>");       jb.setVerticalAlignment (SwingConstants.BOTTOM);       jb.setToolTipText ("Press Cancel to terminate the program.");       jb.addActionListener (this);       jb.setDefaultCapable (true);       getContentPane ().add (jb, BorderLayout.EAST);       getRootPane ().setDefaultButton (jb);       setSize (200, 100);       setVisible (true);    }    public void actionPerformed (ActionEvent e)    {       if (e.getActionCommand ().equals ("Ok"))           System.out.println ("OK was pressed.");       else           System.out.println ("Cancel was pressed.");       System.exit (0);    }   public static void main (String [] args)    {       new ButtonDemo ("Button Demo");    } } 

Note

When you run ButtonDemo, don't forget to make sure that bullet.gif is located in the same directory as ButtonDemo.class. ( bullet.gif is included with the rest of this book's source code.)


In ButtonDemo 's source code, two of JButton 's constructors are called to create buttons. The JButton (String txt, Icon icon) constructor creates a button that displays the characters found in its txt argument and the image found in its icon argument. If only text is required (as in the Cancel button), JButton (String txt) is called.

Note the ImageIcon object that is passed to the Ok button's constructor. The ImageIcon class creates objects that represent icons (because ImageIcon implements the Icon interface). The ImageIcon (String name ) constructor is called with the name of an image file. Internally, it uses MediaTracker to preload the image, and is then responsible for painting this image. By passing an ImageIcon to the JButton constructor, you can have an icon appear alongside a button's text.

When you study ButtonDemo, you'll find two references to the SwingConstants interface (located in the javax.swing package). This interface provides a variety of useful position and orientation constants that are used throughout Swing ”and also can be used in your programs.

As you work your way through ButtonDemo, you'll come across the getContentPane and getRootPane methods . The getContentPane method returns a reference to a Swing program's content pane, which is used like a panel to store components. The getRootPane method returns a reference to a Swing program's root pane, which contains the content pane, and is required to set the default button. (Content and root panes are explored later in this chapter. For now, keep in mind that you must use a content pane in your Swing applications and applets.)

Caution

Two bugs were discovered when testing ButtonDemo. The first bug (which the author refers to as the incredible vibrating button) is related to ToolTips. When you slowly move the mouse pointer into the bottom few pixel rows of either button, these buttons develop a bad case of the shakes, because a ToolTip is being rapidly switched on and off.

The second bug is more ominous. At times, the mnemonic and default button features stop working. You need to reset the computer and re-run the program to get these features to start working again. (These bugs were only discovered when ButtonDemo was tested under the Windows version of the Java 2 Platform ” Windows 98 to be specific. There is a good chance that they might not show up on your platform. Instead, you'll probably have other bugs to deal with!)


Check Boxes

The JCheckBox class (located in javax.swing package) represents a new kind of check box component. This check box has all the features of an abstract button (because JCheckBox indirectly inherits from the AbstractButton class), along with a state that can be toggled on or off (because JCheckBox directly inherits from the JToggleButton class). Figure 16.7 presents a collection of check boxes.

Figure 16.7. As with buttons, Swing's check boxes can be associated with icons.

graphics/16fig07.gif

Figure 16.7's check boxes were created from the CheckBoxDemo application. The source code to this application is presented in Listing 16.2.

Listing 16.2 The CheckBoxDemo Application Source Code
 // CheckBoxDemo.java import javax.swing.*; import java.awt.*; import java.awt.event.*; class CheckBoxDemo extends JFrame implements ActionListener {    CheckBoxDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       ButtonGroup bg = new ButtonGroup ();       JCheckBox north = new JCheckBox ("North");       north.setMnemonic ('o');       north.addActionListener (this);       bg.add (north);       getContentPane ().add (north, BorderLayout.NORTH);       JCheckBox west = new JCheckBox ("West", true);       west.addActionListener (this);       bg.add (west);       getContentPane ().add (west, BorderLayout.WEST);       JCheckBox center = new JCheckBox ("Center");       center.addActionListener (this); //      bg.add (center);       getContentPane ().add (center, BorderLayout.CENTER);       JCheckBox east = new JCheckBox ("East");       east.addActionListener (this);       bg.add (east);       getContentPane ().add (east, BorderLayout.EAST);       JCheckBox south = new JCheckBox ("South",                                        new ImageIcon ("bullet.gif"));       south.addActionListener (this);       bg.add (south);       getContentPane ().add (south, BorderLayout.SOUTH);       pack ();       setVisible (true);    }    public void actionPerformed (ActionEvent e)    {       System.out.println (e.getActionCommand ());    }    public static void main (String [] args)    {       new CheckBoxDemo ("CheckBox Demo");    } } 

When you study CheckBoxDemo, you'll notice a bg object being created from the ButtonGroup class. Check boxes are subsequently added to this object by calling bg 's add method. (In most cases, your check boxes are not added to button groups.)

ButtonGroup has a similar purpose to the AWT's CheckBoxGroup class: It groups its components to form a mutually exclusive group (only one of the components in this group can be selected at any point in time). If you recall, AWT's CheckBoxGroup class was used to achieve this effect ”and create radio buttons. This is no longer needed because Swing introduces a JRadioButton class for this purpose. Check boxes placed in a button group still look like check boxes as you can clearly see in Figure 16.7.

The commented out line prevents the center check box from being part of the button group. As a result, this check box and one other check box (from the group) can be simultaneously selected.

Caution

It's not a good idea to associate an image with a check box because the image hides the check box rectangle and check mark.


Color Choosers

The JColorChooser class (located in javax.swing package) represents a tabbed component with three tabs ( Swatches, HSB, and RGB ) that allow you to choose colors in different ways. After a color has been chosen , the color chooser's selection model fires a change event. By registering a listener, the newly selected color can be used to color one or more components. To see what a color chooser looks like, check out Figure 16.8.

Figure 16.8. Swing's color chooser is useful for selecting a GUI's colors.

graphics/16fig08.gif

Figure 16.8's color chooser and button were created from the ColorChooserDemo application. This application's source code is presented in Listing 16.3.

Listing 16.3 The ColorChooserDemo Application Source Code
 // ColorChooserDemo.java import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; class ColorChooserDemo extends JFrame {    ColorChooserDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       final JButton jb = new JButton ("Ok");       getContentPane ().add (jb, BorderLayout.NORTH);       final JColorChooser cc = new JColorChooser (Color.black);       getContentPane ().add (cc, BorderLayout.SOUTH);       ChangeListener cl = new ChangeListener ()                           {                               public void stateChanged (ChangeEvent e)                               {                                  Color nc = cc.getColor ();                                  jb.setForeground (nc);                               }                           } ;       cc.getSelectionModel ().addChangeListener (cl);       pack ();       setVisible (true);    }    public static void main (String [] args)    {       new ColorChooserDemo ("ColorChooser Demo");    } } 

In ColorChooserDemo, variables jb and cc are marked final because they are local variables that must be accessed from inside an inner class method.

The color chooser's current selection model is retrieved by calling its getSelectionModel method. The model's addChangeListener method is then called to register the current object as a change listener. When a change event is fired , the listener's stateChanged method is called. The color chooser's current color is extracted, by calling getColor, and this color is chosen as the button's foreground color. (Change events and listeners will be discussed later in this chapter.)

Combo Boxes

The JComboBox class (located in javax.swing package) represents a combo box component from which you can choose a single item. This component operates in one of two modes: non-edit and edit.

In non-edit mode, a combo box behaves like an AWT choice component. An item is displayed, along with a down-arrow button. Clicking this button results in the display of a pop-up menu from which you can choose a different item. Figure 16.9 shows a simple non-edit combo box.

Figure 16.9. Swing's combo box can be used in non-edit mode to select an item from a pop-up list of items.

graphics/16fig09.gif

Figure 16.9's combo box was created from the ComboBoxDemo1 application. (Notice the absence of the bottom line on the pop-up menu. This appears to be an example of a Swing painting bug. The second time you pull down the combo box, the bottom line appears.) Listing 16.4 presents source code to ComboBoxDemo1.

Listing 16.4 The ComboBoxDemo1 Application Source Code
 // ComboBoxDemo1.java import javax.swing.*; import java.awt.event.*; import java.util.*; class ComboBoxDemo1 extends JFrame {    ComboBoxDemo1 (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       Vector v = new Vector ();       v.add ("Cow");       v.add ("Horse");       v.add ("Pig");       JComboBox jcb = new JComboBox (v);       getContentPane ().add (jcb);       setSize (200, 50);       setVisible (true);   }   public static void main (String [] args)    {       new ComboBoxDemo1 ("Combo box Demo1");    } } 

Among other constructors, JComboBox provides a constructor that takes a Vector object argument. This vector serves as the model for the combo box. Before calling the constructor, you would populate the vector with objects that will appear in the combo box.

In edit mode, a combo box uses an editor for entering values. You can choose to enter a value via this editor, or you can select an item from the pop-up menu (by clicking the down-arrow button). The contents of the selected item will then appear in the editor. Figure 16.10 shows a simple edit combo box.

Figure 16.10. Swing's combo box can be used in edit mode to either enter an item (via an editor) or select an item from a pop-up list of items. The selected item subsequently appears in the editor.

graphics/16fig10.gif

Figure 16.10's combo box was created from the ComboBoxDemo2 application. This application's source code is nearly identical to ComboBoxDemo1. The only difference is a call to JComboBox 's setEditable method with a Boolean true value, which changes the combo box from its default non-edit mode to edit mode. This is shown in the following code fragment.

 jcb.setEditable (true); 

Swing offers the ability to change the combo box's editor, by calling JComboBox 's setEditor method. For example, if you wanted to enter a color, you might want to choose the color from a palette of colors, as opposed to typing the color's name.

Furthermore, Swing offers the ability to change the combo box's renderer, by calling JComboBox 's setRenderer method. The renderer is responsible for rendering the content of each item in a combo box's pop-up menu. A renderer can even render images. Figure 16.11 shows a combo box with items that integrate images with text.

Figure 16.11. A custom rendered combo box can integrate images with text.

graphics/16fig11.gif

Figure 16.11's combo box was created from the ComboBoxDemo3 application. This application's source code is presented in Listing 16.5.

Listing 16.5 The ComboBoxDemo3 Application Source Code
 // ComboBoxDemo3.java import javax.swing.*; import java.awt.*; import java.awt.event.*; class ComboBoxDemo3 extends JFrame {    ImageIcon [] im;    ComboBoxDemo3 (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       im = new ImageIcon [2];       im [0] = new ImageIcon ("dime.gif");       im [0].setDescription ("Dime");       im [1] = new ImageIcon ("penny.gif");       im [1].setDescription ("Penny");       JComboBox coins = new JComboBox (im);       ComboBoxRenderer renderer = new ComboBoxRenderer ();       renderer.setPreferredSize (new Dimension (200, 100));       coins.setRenderer (renderer);       getContentPane ().add (coins);       pack ();       setVisible (true);    }    public static void main (String [] args)    {       new ComboBoxDemo3 ("Combo box Demo3");    } } class ComboBoxRenderer extends JLabel implements ListCellRenderer {    public ComboBoxRenderer ()    {       setOpaque (true);       setHorizontalAlignment (CENTER);       setVerticalAlignment (CENTER);    }    public Component getListCellRendererComponent (JList list,                                                   Object value,                                                   int index,                                                   boolean selected,                                                   boolean hasfocus)    {       if (selected)       {           setBackground (list.getSelectionBackground ());           setForeground (list.getSelectionForeground ());       }       else       {           setBackground (list.getBackground ());           setForeground (list.getForeground ());       }       ImageIcon icon = (ImageIcon) value;       setText (icon.getDescription ());       setIcon (icon);       return this;    } } 

The setRenderer method takes a single argument, which is an object created from a class that implements the ListCellRenderer interface. If you check the SDK documentation, you'll see that ListCellRenderer provides a single getListCellRendererComponent method.

The getListCellRendererComponent method returns a component that has been configured to display the specified value. This component is capable of rendering a cell ” another name for menu item. After the component has been returned, its paint method is called (behind the scenes) to render the cell. (This is why the ComboBoxRenderer class is derived from JLabel, which will be discussed later in this chapter.)

The getListCellRendererComponent method takes four arguments: a JList argument to identify the combo box or list that contains cells to be rendered; an Object that identifies the current item from the combo box pop-up menu (or list menu); an int that identifies the cell; a Boolean that identifies the cell's selection state (so that an appropriate highlight can be provided during rendering); and a Boolean that indicates if the cell has focus (so that appropriate focus details can be provided during rendering).

You might be curious about the purpose behind the setOpaque method call in the ComboBoxRenderer constructor. This method is called to ensure that all pixels are rendered by the component returned from getListCellRendererComponent. If setOpaque was not called, you would not see the selection highlight because underlying pixels in the nonhighlight color would show through.

File Choosers

The JFileChooser class (located in javax.swing package) provides improved platform-independent modal dialog boxes for choosing files during an open or save operation. (If you recall, the AWT provides a FileDialog class for this purpose.) To see what an Open dialog box looks like, check out Figure 16.12.

Figure 16.12. Swing's file chooser is configured to serve as an Open dialog box. It can also be configured to serve as a Save dialog box.

graphics/16fig12.gif

Figure 16.12's file chooser was created from the FileChooserDemo application. This application's source code is presented in Listing 16.6.

Listing 16.6 The FileChooserDemo Application Source Code
 // FileChooserDemo.java import javax.swing.*; import javax.swing.event.*; import javax.swing.filechooser.*; import java.awt.event.*; import java.io.File; class FileChooserDemo extends JFrame implements ActionListener {    FileChooserDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       JPanel p = new JPanel ();       JButton jb = new JButton ("Open ");       jb.addActionListener (this);       p.add (jb);       jb = new JButton ("Save ");       jb.addActionListener (this);       p.add (jb);       getContentPane ().add (p);       setSize (200, 65);       setVisible (true);    }    public void actionPerformed (ActionEvent e)    {       int retVal;       JFileChooser fc = new JFileChooser ();       if (e.getActionCommand ().equals ("Open "))       {           fc.addChoosableFileFilter (new TextFilter ());           retVal = fc.showOpenDialog (this);       }       else           retVal = fc.showSaveDialog (this);       if (retVal == JFileChooser.APPROVE_OPTION)           System.out.println (fc.getSelectedFile ().getName ());    }    public static void main (String [] args)    {       new FileChooserDemo ("FileChooser Demo");    } } class TextFilter extends FileFilter {    // Accept all directories and txt files.    public boolean accept (File f)    {       if (f.isDirectory ())           return true;       // Check extension for txt.       String s = f.getName ();       int i = s.lastIndexOf ('.');       if (i > 0 && i < s.length () - 1)           if (s.substring (i + 1).toLowerCase ().equals ("txt"))               return true;       return false;    }    public String getDescription ()    {       return "Accepts txt files only.";    } } 

Caution

The source code to TextFilter 's accept method is based on source code presented in the Java Tutorial. However, there is a simpler way to write this method, as demonstrated by the following code fragment.

 public boolean accept (File f) {    return f.isDirectory ()  f.getName ().toLowerCase ().endsWith (".txt"); } 

FileChooserDemo calls the JFileChooser () constructor to create a file chooser. If a user clicks the Open button, a filter is created to narrow the range of displayable filenames.

The filter is created by calling JFileChooser 's addChoosableFileFilter method ”with a reference to a subclass of the abstract FileFilter class (located in the javax.swing.filechooser package). The subclass that's chosen is called TextFilter ” to restrict displayable filenames to those names ending in a .txt file extension. TextFilter overrides the accept and getDescription methods.

Before a filename is displayed, the file chooser calls the filter's accept method. A File object argument, containing the filename, is passed to this method. To retrieve this name, File 's getName method is called. The extension is examined to see if it's acceptable to the filter. If not, a Boolean false value is returned. Otherwise , true is returned.

The getDescription method is called by the file chooser to return a String object, whose contents are displayed in a dialog's Files of Type combo box.

To activate an open file chooser dialog box, call showOpenDialog. In contrast, call showSaveDialog to activate a save file chooser dialog box. These methods cause an appropriate title to be displayed in each dialog box's title bar. Furthermore, they require a single argument that references a Component as the dialog box's parent.

Each dialog box returns a user's response to the dialog box as an int. If the user accepts the dialog box (by clicking either the Open or Save button ”as appropriate), a value identified by JFileChooser 's APPROVE_OPTION constant is returned. However, if the user chooses to cancel the dialog box, CANCEL_OPTION is returned. Assuming an APPROVE_OPTION return value, a call to getSelectedFile causes a reference to a File object ”containing the filename of the approved file ”to be returned.

Version 1.3 of the Java 2 Platform Standard Edition adds two new properties to the JFileChooser class. These properties include control over accept all files ( *.* ) and button removal. (For more information, check out the file chooser section of the new Swing features in the SDK documentation.)

Note

Properties is a JavaBeans term for data that's manipulated by "get" and "set" methods, which conforms to the JavaBeans design pattern.


Labels

Swing's JLabel class offers richer labels than its counterpart AWT Label class. As with buttons, you can create labels that display icons. To see an example, check out Figure 16.13.

Figure 16.13. Swing's labels can display icons.

graphics/16fig13.gif

Note

Figure 16.13 shows a rather large image being used as an icon. This suggests that the JLabel class can be used in place of the AWT's Canvas class for displaying images. If you check out the Java Tutorial, you will see that JLabel is commonly used for this purpose.


Figure 16.13's label was created from the LabelDemo application. This application's source code is presented in Listing 16.7.

Listing 16.7 The LabelDemo Application Source Code
 // LabelDemo.java import javax.swing.*; import java.awt.*; import java.awt.event.*; class LabelDemo extends JFrame {    LabelDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       ImageIcon icon = new ImageIcon ("meteorcrater.jpg");       JLabel jl = new JLabel ("Arizona MeteorCrater", icon,                               JLabel.CENTER);       jl.setHorizontalTextPosition (SwingConstants.LEADING);       getContentPane ().add (jl);       setSize (750, 400);       setVisible (true);    }    public static void main (String [] args)    {       new LabelDemo ("Label Demo");    } } 

LabelDemo creates a label combining text and an icon. The label is centered in its container by using JLabel.CENTER.

Note

By the way, if you look in the JLabel class, you won't find a constant called CENTER. This constant is located in the SwingConstants interface. Because JLabel implements SwingConstants, it's legal to specify JLabel.CENTER. Alternatively, you could just as easily specify SwingConstants.CENTER.


JLabel 's setHorizontalTextPosition method is called to determine the location of the label's text, relative to the icon. The LEADING constant indicates that this text should appear to the left of the icon. (The default is TRAILING. )

Lists

The Jlist class (located in javax.swing package) represents a list component from which you can choose one or more items. By default, you can choose multiple items. However, it's also possible to create a list from which a user can only choose a single item. Figure 16.14 shows a simple list.

Figure 16.14. Swing's list can be used to select single or multiple items.

graphics/16fig14.gif

Figure 16.14's list was created from an application called ListDemo. This application's source code is presented in Listing 16.8.

Listing 16.8 The ListDemo Application Source Code
 // ListDemo.java import javax.swing.*; import java.awt.event.*; import java.util.*; class ListDemo extends JFrame {    ListDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       Vector v = new Vector ();       v.add ("Cow");       v.add ("Pig");       v.add ("Tiger");       v.add ("Aardvark");       v.add ("Lion");       v.add ("Horse");       v.add ("Cheetah");       v.add ("Zebra");       v.add ("Seal");       v.add ("Donkey");       v.add ("Gorilla");       v.add ("Chimpanzee");       JList animals = new JList (v);       animals.setSelectedIndex (0);       // Uncomment the following line to make it possible to ONLY       // select a single item at a time.       // animals.setSelectionMode (ListSelectionModel.SINGLE_SELECTION);       getContentPane ().add (animals);       setSize (250, 200);       setVisible (true);    }    public static void main (String [] args)    {       new ListDemo ("List Demo");    } } 

ListDemo calls the JList (Vector listData) constructor to create a list. Internally, listData is converted into a ListModel object ”an object created from a class that implements the ListModel interface, and designed to serve as the list's model.

By subclassing the abstract AbstractListModel class (located in javax.swing package), you can create your own list model and pass it to the JList (ListModel listData) constructor. (This class implements ListModel. )

Tip

You must explicitly specify the index of a list's highlighted item. If you don't, you won't see a highlighted item when the list is displayed.


You might be wondering why only nine of the twelve items are displayed. The reason is due in part to the use of the setSize method, rather than pack. If pack had been specified, all twelve items would be visible. However, if you had a long list of items (such as 35), not even the use of pack would result in all items being displayed. The reason is that lists do not possess an inherit scrolling capability. Scrolling can only be achieved by inserting a list into a scroll pane ”a topic that is discussed later in this chapter.

Menus, Pop-Up Menus , and Toolbars

Menus, pop-up menus, and toolbars are staples of modern GUIs. In Chapter 14, you learned how to use the AWT's various classes to incorporate menus and pop-up menus. However, toolbars couldn't be explored because the AWT does not support these components. Fortunately, Swing supports toolbars. In fact, Swing provides the following menu and toolbar classes:

  • JCheckBoxMenuItem

  • JMenuBar

  • JMenu

  • JMenuItem

  • JPopupMenu

  • JRadioButtonMenuItem

  • JToolBar

As far as menu classes are concerned , you've already had a taste of what they can offer ”except for JRadioButtonMenuItem. This latter class is used to create objects that represent a group of mutually exclusive menu items.

Suppose you want to create a toolbar. How would you accomplish this task? For an answer, check out the following code fragment:

 JToolBar tb = new JToolBar (); JButton jb = new JButton ("D"); jb.addActionListener (this); tb.add (jb); jb = new JButton ("S"); jb.addActionListener (this); tb.add (jb); getContentPane ().add (tb, BorderLayout.NORTH); 

As you can see, a toolbar is a collection of buttons. You attach an action listener to each button to handle a toolbar button press event. You then call the toolbar's add method to add each button to the toolbar. After you're finished, you can add the toolbar to your program's content pane. Listing 16.9 presents source code to the MPTDemo application that demonstrates putting together a toolbar, along with a menu bar, menus, menu items, and a pop-up menu. (For more information, check out the Java Tutorial and SDK documentation.)

Listing 16.9 The MPTDemo Application Source Code
 // MPTDemo.java import javax.swing.*; import java.awt.*; import java.awt.event.*; class MPTDemo extends JFrame implements ActionListener {    JPopupMenu pm;    MPTDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       JMenu plaf = new JMenu ("PLAF");       pm = new JPopupMenu ();       JMenuItem mi = new JMenuItem ("Default PLAF");       mi.addActionListener (this);       plaf.add (mi);       mi = new JMenuItem ("Default PLAF");       mi.addActionListener (this);       pm.add (mi);       mi = new JMenuItem ("System PLAF");       mi.addActionListener (this);       plaf.add (mi);       mi = new JMenuItem ("System PLAF");       mi.addActionListener (this);       pm.add (mi);       JMenuBar mb = new JMenuBar ();       mb.add (plaf);       setJMenuBar (mb);       JToolBar tb = new JToolBar ();       JButton jb = new JButton ("D");       jb.addActionListener (this);       tb.add (jb);       jb = new JButton ("S");       jb.addActionListener (this);       tb.add (jb);       getContentPane ().add (tb, BorderLayout.NORTH);       jb = new JButton ("Hello World!");       jb.setPreferredSize (new Dimension (200, 50));       getContentPane ().add (jb, BorderLayout.SOUTH);       jb.addMouseListener (new MouseAdapter ()                            {                               public void mousePressed (MouseEvent e)                               {                                  if (e.isPopupTrigger ())                                      pm.show (e.getComponent (),                                               e.getX (),                                               e.getY ());                               }                               public void mouseReleased (MouseEvent e)                               {                                  if (e.isPopupTrigger ())                                      pm.show (e.getComponent (),                                               e.getX (),                                               e.getY ());                               }                            });       pack ();       setVisible (true);    }    public void actionPerformed (ActionEvent e)    {       String s = e.getActionCommand ();       String lnfName;       if (s.equals ("D")  s.equals ("Default PLAF"))           lnfName = UIManager.getCrossPlatformLookAndFeelClassName ();       else           lnfName = UIManager.getSystemLookAndFeelClassName ();       try       {           UIManager.setLookAndFeel (lnfName);       }       catch (Exception e2) {  System.out.println (e2);}       SwingUtilities.updateComponentTreeUI (this);       pack ();    }    public static void main (String [] args)    {       new MPTDemo ("Menu/Pop-up Menu/Toolbar Demo1");    } } 

Caution

You might be wondering why code has been duplicated in MPTDemo. For example, why do the Default PLAF and System PLAF menu items have to be created twice, and a separate action listener attached to each menu item. Unfortunately, if you try to share a single menu item between a JMenu and JPopupMenu, the JMenu will never show its items (at least not when run under Windows 98).


By using JToolBar, you get docking functionality for free. Just grab to the left of the toolbar and drag it to either the top, left, right, or bottom, or outside the window to get a free-standing toolbar.

Note

You can experiment with docking in MPTDemo. However, you will need to resize the window, otherwise the toolbar will be partially hidden by the button when docked to either the left or right of the window. (You might want to change the pack method call to a setSize method call ”to create a window of a sufficiently large size .) By the way, because the button occupies the bottom portion of the window, you will not be able to dock the toolbar to the window's bottom.


Progress Bars

When an activity is going to take a long time to complete, many applications use progress bars to show the activity's current status, and to help a user know that the activity is continuing. Figure 16.15 shows a progress bar.

Figure 16.15. Swing's progress bar provides a visual cue to an activity's current status, and lets the user know that an activity is progressing.

graphics/16fig15.gif

Unlike the AWT, Swing provides a progress bar component via its JProgressBar class (located in the javax.swing package). Essentially, JProgressBar displays an integer value within a bounded interval. To create a JProgressBar (with either default or explicit intervals), you would call any of several constructors, as demonstrated by the following code fragment:

 // Create a horizontal progress bar bounded by 0 and 100 (the default). JProgressBar jpbar1 = new JProgressBar (); // Create a horizontal progress bar bounded by 0 and 50. JProgressBar jpbar2 = new JProgressBar (0, 50); // Create a vertical progress bar bounded by 20 and 56. JProgressBar jpbar3 = new JProgressBar (JProgressBar.VERTICAL, 20, 56); 

After you have a progress bar, you can call the setValue method to update its model (which just happens to be an object created from a class that implements the BoundedRangeModel interface). In turn , the model updates the progress bar's UI delegate so that the current status is visible to users.

You can also call the setMinimum and setMaximum methods to change the progress bar's minimum and maximum bounds. (To learn more about the various methods that are offered by JProgressBar, check out the SDK documentation.)

Figure 16.15's progress bar was created from an application called ProgressBarDemo1. This application demonstrates creating a simple progress bar and calling some of its methods. The source code is presented in Listing 16.10.

Listing 16.10 The ProgressBarDemo1 Application Source Code
 // ProgressBarDemo1.java import javax.swing.*; import java.awt.*; import java.awt.event.*; class ProgressBarDemo1 extends JFrame {    JProgressBar pbar;    ProgressThread pthread;    ProgressBarDemo1 (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       pbar = new JProgressBar ();       getContentPane ().add (pbar, BorderLayout.NORTH);       JPanel jp = new JPanel ();       ActionListener l = new ActionListener ()                          {                              public void actionPerformed (ActionEvent e)                              {                                 if (pthread == null                                      !pthread.isAlive ())                                 {                                     pthread = new ProgressThread (pbar);                                     pthread.start ();                                 }                              }                          } ;       JButton jb = new JButton ("Start");       jb.addActionListener (l);       jp.add (jb);       l = new ActionListener ()           {               public void actionPerformed (ActionEvent e)               {                  if (pthread != null)                      pthread.setStop (true);               }           } ;       jb = new JButton ("Stop");       jb.addActionListener (l);       jp.add (jb);       getContentPane ().add (jp, BorderLayout.SOUTH);       setSize (200, 100);       setVisible (true);    }    public static void main (String [] args)    {       new ProgressBarDemo1 ("ProgressBar Demo1");    } } class ProgressThread extends Thread {    JProgressBar pbar;    boolean stopped;    ProgressThread (JProgressBar pbar)    {       this.pbar = pbar;    }    void setStop (boolean state)    {       stopped = state;    }    public void run ()    {       int min = 0;       int max = 50;       pbar.setMinimum (min);       pbar.setMaximum (max);       pbar.setValue (min);       for (int i = min; i <= max; i++)            if (stopped)                break;            else            {                pbar.setValue (i);                try                {                   Thread.sleep (100);                }                catch (InterruptedException e) { }            }    } } 

Progress bars require a background thread to keep them updated, and ProgressBarDemo1 is no exception. You'll notice that its setValue method is called in the thread's run method to update the progress bar's model (which also updates the progress bar's UI delegate).

Note

Although thread safety hasn't been discussed, the setValue method is one of Swing's few thread-safe methods. As a result, it can be called from other threads. (Many of Swing's methods cannot be called from another thread. Later in this chapter, this threading issue will be addressed.)


In addition to JProgressBar, Swing provides the ProgressMonitor and ProgressMonitorInputStream classes. ProgressMonitor is used to automatically display a progress dialog box if a task takes longer than a minimum amount of time. (To get a sense of how this class is used, you'll want to check out the source code to the ProgressBarDemo2 application, which is included with the rest of this book's source code.) ProgressMonitorInputStream is similar to ProgressMonitor. However, it's designed for monitoring stream I/O operations. (Streams are discussed in Chapter 21, "Streams, Files, and Serialization.")

Radio Buttons

The JRadioButton class (located in javax.swing package) represents a radio button. This radio button has all the features of an abstract button (because JRadioButton indirectly inherits from the AbstractButton class), along with a state that can be toggled on or off (because JRadioButton directly inherits from the JToggleButton class).

Unlike check boxes, radio buttons are normally added to a button group. Only one of the buttons in this group can be selected. This allows a user to toggle between several possibilities. In contrast, check boxes are not normally added to a button group. Each check box represents a separate element of state and allows a user to toggle between a true or false value. Knowing whether to use check boxes or radio buttons is one of the challenges that faces GUI designers. Figure 16.16 presents a collection of radio buttons.

Figure 16.16. Swing's radio buttons are typically organized in button groups.

graphics/16fig16.gif

Figure 16.16's radio buttons were created from the RadioButtonDemo application. The source code to this application is presented in Listing 16.11.

Listing 16.11 The RadioButtonDemo Application Source Code
 // RadioButtonDemo.java import javax.swing.*; import java.awt.event.*; class RadioButtonDemo extends JFrame {    RadioButtonDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       JRadioButton rb1 = new JRadioButton ("Male");       rb1.setMnemonic (KeyEvent.VK_M);       rb1.setActionCommand ("Male");       rb1.setSelected (true);       JRadioButton rb2 = new JRadioButton ("Female");       rb2.setMnemonic (KeyEvent.VK_F);       rb2.setActionCommand ("Female");       rb2.setSelected (true);       JRadioButton rb3 = new JRadioButton ("Indeterminate");       rb3.setMnemonic (KeyEvent.VK_I);       rb3.setActionCommand ("Indeterminate");       rb3.setSelected (true);       ButtonGroup bg = new ButtonGroup ();       bg.add (rb1);       bg.add (rb2);       bg.add (rb3);       JPanel jp = new JPanel ();       jp.add (rb1);       jp.add (rb2);       jp.add (rb3);       getContentPane ().add (jp);       pack ();       setVisible (true);    }    public static void main (String [] args)    {       new RadioButtonDemo ("RadioButton Demo");    } } 

Version 1.3 of the Java 2 Platform Standard Edition corrects an oversight with button groups. In previous versions, you could establish the current button group, by calling ButtonModel 's setGroup method. However, no one thought to provide a getGroup method that returns this group. Adding getGroup to ButtonModel would result in code being broken. Therefore, a getGroup method has been added to the DefaultButtonModel class (which is the default model used by buttons, check boxes, and radio buttons). If you create a new button model class, you should provide a getGroup method ”or risk breaking code.

Sliders and Scrollbars

Sliders and scrollbars are similar components. They both allow users to select a value from a range of values. However, sliders are intended for selecting values that don't represent percentages (and display major/minor tick marks and numeric labels), whereas scrollbars are intended for selecting percentages. For example, suppose you have a slider and a scrollbar that both range from 0 to 100. If you select 25 from the slider, you are returning the number 25 from the slider's range. With the scrollbar, 25 refers to 25%. This is a subtle difference, but important enough to warrant two different classes: JSlider and JScrollBar (located in the javax.swing package).

Note

Because JScrollBar is similar to the AWT's Scrollbar class, and because Scrollbar was explored in a previous chapter, JScrollBar won't be discussed here.


Figure 16.17 shows a slider (along with a scrollbar).

Figure 16.17. Swing's sliders can display major/minor tick marks with numeric labels.

graphics/16fig17.gif

JSlider provides several constructors for creating horizontal and vertical sliders. By default, tick marks and labels aren't displayed. The following code fragment calls one of these constructors to create a vertical slider with a bounded interval from 0 to 50, and an initial value of 25:

 JSlider js = new JSlider (JSlider.VERTICAL, 0, 50, 25); 

You can call various methods to customize the appearance and behavior of a slider. For example, setPaintTicks causes tick marks to be displayed, setPaintLabels causes labels to be displayed (only if there is sufficient major tick mark spacing), setMajorTickSpacing sets the amount of spacing between major tick marks, setMinorTickSpacing sets the amount of spacing between minor tick marks, and setSnapToTicks forces the knob to only point to tick marks.

Figure 16.17's slider and scroll bar were created from the SSDemo application. This application's source code is presented in Listing 16.12.

Listing 16.12 The SSDemo Application Source Code
 // SSDemo.java import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; class SSDemo extends JFrame implements ChangeListener {    JLabel sliderValue;    SSDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       JSlider js = new JSlider (JSlider.VERTICAL, 0, 50, 25);       js.setPaintTicks (true);       js.setPaintLabels (true);       js.setMajorTickSpacing (10);       js.setMinorTickSpacing (5); //      js.setSnapToTicks (true);       js.addChangeListener (this);       JPanel jp = new JPanel ();       jp.add (js);       getContentPane ().add (jp, BorderLayout.WEST);       JScrollBar jsb = new JScrollBar (JScrollBar.VERTICAL, 25, 5,                                        0, 50);       jp = new JPanel ();       jp.add (jsb);       getContentPane ().add (jp, BorderLayout.EAST);       sliderValue = new JLabel ("Value = 25");       getContentPane ().add (sliderValue, BorderLayout.SOUTH);       setSize (250, 250);       setVisible (true);    }    public void stateChanged (ChangeEvent e)    {       sliderValue.setText ("Value = " + ((JSlider) e.getSource ())                                                     .getValue ());    }    public static void main (String [] args)    {       new SSDemo ("Slider/Scrollbar Demo");    } } 

SSDemo displays a vertical slider, vertical scrollbar, and a label. When you move the slider's knob, a change event is fired which results in the label being updated. (Nothing happens when you move the scrollbar.)

By default, the slider does not snap to ticks . However, you can observe this behavior by uncommenting the js.setSnapToTicks (true); method call.

Tables

One of the application categories that resulted in the success of personal computers is the spreadsheet. Spreadsheets present data in a table-oriented view. Swing's JTable class makes it possible to incorporate such tables into your programs. (This class is located in the javax.swing package.) Figure 16.18 illustrates a simple two-column table.

Figure 16.18. Creating tables with Swing is easy.

graphics/16fig18.gif

Listing 16.13 presents source code to the TableDemo1 application. This application was used to create Figure 16.18's GUI.

Listing 16.13 The TableDemo1 Application Source Code
 // TableDemo1.java import javax.swing.*; import java.awt.event.*; class TableDemo1 extends JFrame {    TableDemo1 (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       String [][] data =       {          {  "1", "Introduction" } ,          {  "2", "What Java can do for you" } ,          {  "3", "Java architecture" } ,          {  "4", "Installing Java" } ,          {  "5", "JDK tools" }       } ;       String [] columnNames = {  "Chapter Number", "Chapter Title" } ;       JTable jt = new JTable (data, columnNames);       getContentPane ().add (jt);       setSize (300, 90);       setVisible (true);    }    public static void main (String [] args)    {       new TableDemo1 ("Table Demo1");    } } 

Note

As with ListDemo, TableDemo1 does not display all rows of information. Once again, the reason is due to JTable 's lack of support for scrolling. To remedy this situation, the JTable would need to be inserted into a scroll pane ”a topic that is discussed later in this chapter.


TableDemo1 creates a table, by calling a JTable constructor. For example, JTable (Object [][] rowData, Object [] columnNames) is called to establish a two-column table. A two-dimensional array of objects is passed. Each array row contains the data for all columns in a table row. Furthermore, a one-dimensional array of column names is passed to add some class to this table.

Although it's not hard to create a basic table, you lose a lot of flexibility. For example, you can modify the data in any of the table's cells. The only way to create a read-only table (amongst other things), is to provide your own table model. As a result, Listing 16.14 presents source code to the TableDemo2 application, which does just this.

Listing 16.14 The TableDemo2 Application Source Code
 // TableDemo2.java import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import java.awt.event.*; class TableDemo2 extends JFrame implements TableModelListener {    TableDemo2 (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       JTable jt = new JTable (new MyTableModel ()); //      jt.setSelectionMode (ListSelectionModel.SINGLE_SELECTION);       jt.getModel ().addTableModelListener (this);       getContentPane ().add (jt);       setSize (300, 90);       setVisible (true);    }    public void tableChanged (TableModelEvent e)    {       System.out.println ("Affected column = " + e.getColumn ());       System.out.println ("First affected row = " + e.getFirstRow ());       System.out.println ("Last affected row = " + e.getLastRow ());    }    public static void main (String [] args)    {       new TableDemo2 ("Table Demo2");    } } class MyTableModel extends AbstractTableModel {    private String [][] data =    {       {  "1", "Introduction" } ,       {  "2", "What Java can do for you" } ,       {  "3", "Java architecture" } ,       {  "4", "Installing Java" } ,       {  "5", "JDK tools" }    };    private String [] columnNames =    {       "Chapter Number",       "Chapter Title"    } ;    public int getRowCount ()    {       return data.length;    }    public int getColumnCount ()    {       // Assume that all rows have the same number of columns.       // As you probably know, it is possible for each row in       // a Java array to have a different number of columns.       return data [0].length;    }    public Object getValueAt (int row, int column)    {       return data [row] [column];    }    public boolean isCellEditable (int row, int column)    {       return (column == 1) ? false : true;    }    public void setValueAt (Object value, int row, int col)    {       if (value instanceof String)       {           data [row] [col] = (String) value;          // Must fire a table model event so that views are           // notified about the change to the table's model.          fireTableCellUpdated (row, col);       }    } } 

Note

TableDemo2 subtly raises the hotly-debated issue of readability versus terseness in source code. If you look at the isCellEditable method, you'll notice the return (column == 1) ? true : false; statement. This statement could be alternatively specified as return (column != 1);. Many developers consider the former statement to be more readable than the second statement ”which is considered to be rather terse. The reason the first statement is considered more readable is that its meaning " jumps out at most people," whereas one might need to think for a few moments to comprehend the meaning of the second statement. In some coding shops (where this author has worked), readability was preferred over terseness. Although some developers suggest that readability results in extra compiled code (which can slow down execution), an optimizing compiler can generate the same code for either statement. As a result, there's often no need to resort to terseness.


TableDemo2 demonstrates quite a few items. First, JTable 's setSelectionMode method can be called to change a table from the default multiple selection mode (in which multiple rows can be selected) to a single selection mode (in which only one row can be selected).

Second, by creating a MyTableModel class that extends the abstract AbstractTableModel class (located in the javax.swing.table package), and overriding key methods, a new model is established that simplifies table data management. The isCellEditable method is one of these overridden methods. This method determines if a cell is editable or read only. When a user moves from cell to cell, this method is called with arguments that identify the current cell. If a Boolean false value is returned, the cell is read only. TableDemo2 ensures that all cells in the second column are read-only by returning false, from isCellEditable, when the user is in column 1. (Row and column indices are zero-based .)

Finally, the table's model is retrieved (by calling getModel ) and its addTableModelListener method is called to register the application as a listener to changes in the table's model. So, what changes this model? Look at MyTableModel and note the setValueAt method. When this method is called, it changes model data. It also calls the model's fireTableCellUpdated method to alert all table model listeners, by calling their tableChanged methods.

There is so much more that could be said about tables. Cell editors is one example. You would use a cell editor (such as a combo box) to more conveniently select or enter cell data. To learn more, you are encouraged to explore your SDK documenation and the Java Tutorial.

Version 1.3 of the Java 2 Platform Standard Edition makes several improvements to the JTable class. These improvements include a performance benefit for tables with many columns, the ability to dynamically change an individual row's height, simplified creation of nonstandard editor components (by way of the AbstractCellEditor class), and improved handling of inter-cell spacing. Because of these improvements, quite a few methods have been deprecated. (For more information, check out the table section of the new Swing features in the SDK documentation.)

Text

It would be an understatement to say that Swing has enlarged upon the AWT's TextArea and TextField classes (for working with text). In fact, so much work has been done on enhancing Swing's text component capabilities, that an entire chapter is needed to adequately cover this material. Unfortunately, there just isn't enough room to provide such coverage in this chapter. Therefore, you're strongly encouraged to review the excellent coverage of this material in the Java Tutorial (and SDK documentation).

Swing's text component classes descend from JTextComponent. This class identifies what it means to be a text component. As you work through the SDK documentation, you'll encounter JTextField, JTextArea, JPasswordField, JEditorPane, and a lot more classes. To give you a small sense of what Swing's text components can do, take a quick look at the JEditorPane class.

Have you ever wanted to create a Web browser? If the answer is yes, you'll need to explore Swing's JEditorPane class. Not only does JEditorPane make it possible to view Web pages, you can also use this class to view RTF (Rich Text Format) files. (Microsoft Word is capable of generating RTF files.) Figure 16.19 shows part of the main Web page from the Java Tutorial rendered by JEditorPane.

Figure 16.19. Swing's editor pane can be used to view a Web page.

graphics/16fig19.gif

Listing 16.15 presents source code to the EditorDemo application. This application was used to provide the GUI in Figure 16.19.

Listing 16.15 The EditorDemo Application Source Code
 // EditorDemo.java import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.net.*; class EditorDemo extends JFrame                  implements ActionListener, HyperlinkListener {    JEditorPane view;    JTextField commandLine;    EditorDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       commandLine = new JTextField ();       commandLine.addActionListener (this);       getContentPane ().add (commandLine, BorderLayout.NORTH);       view = new JEditorPane ();       view.setEditable (false);       view.setPreferredSize (new Dimension (400, 400));       view.addHyperlinkListener (this);       getContentPane ().add (view, BorderLayout.CENTER);       pack ();       setVisible (true);    }    public void actionPerformed (ActionEvent e)    {       try       {          URL url = new URL (e.getActionCommand ());          view.setPage (url);          commandLine.setText (url.toExternalForm ());       }       catch (MalformedURLException e2)       {          System.out.println ("Bad URL: " + e.getActionCommand ());       }       catch (java.io.IOException e2) { }    }    public void hyperlinkUpdate (HyperlinkEvent e)    {       try       {          view.setPage (e.getURL ());          commandLine.setText (e.getURL ().toExternalForm ());       }       catch (java.io.IOException e2) { }    }    public static void main (String [] args)    {       new EditorDemo ("Editor Demo");    } } 

Caution

To run EditorDemo from behind a firewall, you could issue the following command line:

 java -Dhttp.proxyHost=<host> -Dhttp.proxyPort=<port> EditorDemo 

Replace <host> with the appropriate proxy host and <port> with the appropriate port.


EditorDemo uses two different components: a JEditorPane and a JTextField. The editor pane displays a Web page, and the text field holds the address of this Web page. To use EditorDemo, type an address in the text field and press the Return key.

You can travel from Web page to Web page by positioning the mouse pointer over a link.

Note

To force EditorDemo to only follow a link when the link is clicked, you need place the following code fragment before the try/catch construct in the hyperlinkUpdate method:

 if (e.getEventType () != HyperLinkEvent.EventType.ACTIVATED)     return; 

In response to moving the mouse pointer over a link, the editor pane fires a hyperlink event, which results in a HyperLinkEvent object being passed to a hyperlink listener's hyperLinkUpdate method. This method calls the editor pane's setPage method to establish the new Web page as the current Web page. Furthermore, the address URL's toExternalForm method is called to present a user-oriented Web address (for the new Web page) in the text field.

Caution

JEditorPane only supports HTML 3.2. As a result, it cannot correctly render Web pages that use more recent HTML tags.


Trees

Trees are important components in modern GUIs, and are popular with programs that display hierarchical data (such as directories and files).

Trees are made up of nodes (structures of data). Each node represents either a leaf or a branch. A leaf is a distinct element that has no children (subelements). In other words, leaves cannot be expanded (opened) and collapsed (closed). In contrast, a branch is a distinct element that can have children. Furthermore, branches can be expanded and collapsed.

Swing introduces the JTree class (located in the javax.swing package) for working with trees. Furthermore, Swing provides the DefaultMutableTreeNode class to meet most (if not all) of your node-based needs. (This class is located in the javax.swing.tree package.)

To give you a small taste for trees, Listing 16.16 presents source code to the TreeDemo application.

Listing 16.16 The TreeDemo Application Source Code
 // TreeDemo.java import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; import java.awt.event.*; class TreeDemo extends JFrame implements TreeSelectionListener {    TreeDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       DefaultMutableTreeNode rootNode = createNodes ();       JTree tree = new JTree (rootNode);       tree.addTreeSelectionListener (this);       tree.setRootVisible (true);       getContentPane ().add (tree);       setSize (400, 300);       setVisible (true);    }    DefaultMutableTreeNode createNodes ()    {       DefaultMutableTreeNode root;       root = new DefaultMutableTreeNode ("Java");       DefaultMutableTreeNode resources;       resources = new DefaultMutableTreeNode ("Resources");       DefaultMutableTreeNode books;       books = new DefaultMutableTreeNode ("Books");       books.add (new DefaultMutableTreeNode ("Java 2 By Example"));       books.add (new DefaultMutableTreeNode ("Special Edition Using " +                                              "Java 2 Platform " +                                              "Standard Edition"));       resources.add (books);       DefaultMutableTreeNode magazines;       magazines = new DefaultMutableTreeNode ("Magazines");       magazines.add (new DefaultMutableTreeNode ("JavaWorld"));       resources.add (magazines);       DefaultMutableTreeNode webSites;       webSites = new DefaultMutableTreeNode ("Web sites");       webSites.add (new DefaultMutableTreeNode ("Gamelan"));       webSites.add (new DefaultMutableTreeNode ("Javasoft"));       resources.add (webSites);       root.add (resources);       DefaultMutableTreeNode tools;       tools = new DefaultMutableTreeNode ("Tools");       tools.add (new DefaultMutableTreeNode ("JDK"));       tools.add (new DefaultMutableTreeNode ("Kawa"));       tools.add (new DefaultMutableTreeNode ("Visual Age for Java"));       root.add (tools);       return root;    }    public void valueChanged (TreeSelectionEvent e)    {       TreePath path;       path = (TreePath) e.getNewLeadSelectionPath ();       System.out.println (path);    }    public static void main (String [] args)    {       new TreeDemo ("Tree Demo");    } } 

TreeDemo uses DefaultMutableTreeNode to build and connect several nodes into a tree. Furthermore, it registers itself as a listener for tree selection events. When a tree selection event is fired, TreeDemo 's valueChanged method is called. This method calls TreeSelectionEvent 's getNewLeadSelectionPath method to obtain the path from the root branch node to the selected branch or leaf node. Figure 16.20 shows TreeDemo 's tree with all its branches expanded.

Figure 16.20. Swing's trees make it easier to navigate hierarchies.

graphics/16fig20.gif

Note

As with JList and JTable, JTree is not inherently scrollable. To remedy this situation, the JTable object would need to be inserted into a scroll pane ”a topic that is discussed later in this chapter.


Version 1.3 of the Java 2 Platform Standard Edition adds several new properties and methods to the JTree class. For example, the new toggle click property can be used for configuring how many mouse clicks are needed to expand and collapse a node. (For more information, check out the tree section of the new Swing features in the SDK documentation. This information can be found online at http://java.sun.com/j2se/1.3/docs/relnotes/features.html.)

Exploring Containers

Swing provides four top-level container classes that correspond to the AWT's Applet, Dialog, Frame, and Window classes: JApplet, JDialog, JFrame, and JWindow. Swing applets use instances of JApplet as their top-level containers, whereas Swing applications use JFrame. Furthermore, instances of JDialog serve as top-level containers for dialog boxes with users, and instances of JWindow represent pop-up windows that serve various needs (not met by the other top-level containers). In either case, a dialog box or window must be associated with an applet or frame.

Swing's top-level containers are organized into a series of panes: root pane, layered pane, content pane, and glass pane. The root pane manages the layered pane, content pane, and glass pane. The layered pane organizes components into a Z-order to handle situations in which components overlap. The content pane contains all components except for the menu bar. Finally, the glass pane (hidden and transparent by default) can be used to paint content over multiple components and intercept events.

A reference to the root pane can be obtained, by calling a top-level container's getRootPane method. You can use this reference to add a default button to a container (among other things). However, as a rule, you'll rarely need to access this pane. (Top-level containers have a setRootPane method, but it's protected.)

A reference to the content pane can be obtained by calling a top-level container's getContentPane method. (You've already seen quite a few examples of the content pane.) By default, the content pane uses a border layout manager. If you like, you can replace the current content pane by calling setContentPane, as the following code fragment demonstrates:

 JPanel jp = new JPanel (); setContentPane (jp); 

A reference to the layered pane can be obtained by calling a top-level container's getLayeredPane method. You can also replace the current layered pane by calling setLayeredPane. (You create a new layered pane by using the JLayeredPane class.)

A layered pane consists of several layers. Each layer represents a Z-order position for handling situations in which components overlap. For example, when you activate a pop-up menu, you don't want an underlying button to appear over the pop-up. (Your users wouldn't be too happy.) By using layers, Swing orchestrates painting so that overlap is properly handled. Table 16.1 presents the various layers (in a back to front manner ”the frame content layer is the closest to the back of the Z-order and the drag layer is the closest to the front).

Table 16.1. JLayeredPane 's Various Layers
Layer Constant Description
FRAME_CONTENT_LAYER This layer positions the applet's/frame's content pane and menu bar. The root pane adds the menu bar and content pane to its layered pane at this layer.
DEFAULT_LAYER This is the default layer where most components are placed.
PALETTE_LAYER Floating toolbars and palettes use this layer.
MODAL_LAYER Modal internal frame dialog boxes are placed in this layer.
POPUP_LAYER Pop-up menus are placed in this layer, so they can appear over almost everything else.
DRAG_LAYER A component is moved to this layer when dragging, and returned to its own layer when dropped.

A reference to the glass pane can be obtained by calling a top-level container's getGlassPane method. You can also replace the current glass pane by calling setGlassPane. (The Java Tutorial provides an interesting example of using a glass pane.)

In addition to these panes, there are other panes that can be used for special purposes. These panes include desktop, scroll, tabbed, option, and split.

Desktop Panes and Internal Frames

Microsoft Windows 3.0 and 3.1 introduced the world to the idea of multiple document interfaces (MDI). Basically, each document would be placed in a separate window, and the various windows would be managed by a container window. Microsoft decided to abandon this idea when it released Windows 95. However, MDI is still useful.

Swing supports MDI through its JDesktopPane and JInternalFrame classes (located in the javax.swing package). Figure 16.21 shows what a GUI using these classes looks like.

Figure 16.21. Swing's desktop pane and internal frames are used to achieve MDI.

graphics/16fig21.gif

The following code fragment, taken from the DesktopPaneDemo program (whose source code is included with the rest of this book's source code), shows how to set up a desktop pane and one internal frame.

 JDesktopPane desktop = new JDesktopPane (); JInternalFrame jf1 = new JInternalFrame ("First Window"); JLabel jl = new JLabel ("Label #1"); jf1.getContentPane ().add (jl); jf1.setSize (150, 75); jf1.setVisible (true); desktop.add (jf1); 
Scroll Panes and Viewports

JList, JTable, and JTree don't scroll their data. Unlike JComboBox, there is no inherit scrolling ability in these classes. As a result, if some of the data is not visible, you just can't access it. However, Swing comes to the rescue, by providing a common scrolling component known as a scroll pane.

Scroll panes are created from the JScrollPane class. This class works in conjunction with JViewPort to manage a container's scrolling needs. Figure 16.22 shows what the TreeDemo program would look like with scrollbars.

Figure 16.22. Swing's JScrollPane and JViewPort classes help users access all data.

graphics/16fig22.gif

Note

The program used to generate this figure is called ScrollPaneDemo3. It contains the same source code as TreeDemo, except for class name changes and some scrolling code. ScrollPaneDemo3 ” and two other scroll pane demo programs that add scrolling to a list and table ”are included with this book's source code.


The following code fragment, taken from ScrollPaneDemo3, shows how easy it is to add scrolling behavior to a component:

 JScrollPane jsp = new JScrollPane (); jsp.setViewportView (tree); getContentPane ().add (jsp); 
Tabbed Panes

Tabbed panes work like the AWT's card layout manager. They allow you to place components on different tabs and switch from one tab to another. Swing's JTabbedPane class (located in the javax.swing package) is used to create tabbed panes.

Listing 16.17 presents source code to the TabbedPaneDemo program that demonstrates tabbed panes.

Listing 16.17 The TabbedPaneDemo Application Source Code
 // TabbedPaneDemo.java import javax.swing.*; import java.awt.event.*; class TabbedPaneDemo extends JFrame {    TabbedPaneDemo (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       JTabbedPane tabs = new JTabbedPane (SwingConstants.BOTTOM);       tabs.addTab ("First tab", new JLabel ("Hello!"));       tabs.addTab ("Second tab", new JButton ("Goodbye!"));       getContentPane ().add (tabs);       setSize (250, 200);       setVisible (true);    }    public static void main (String [] args)    {       new TabbedPaneDemo ("TabbedPane Demo");    } } 

TabbedPaneDemo calls the JTabbedPane (int location) constructor, with a location argument set to SwingUtilities.BOTTOM, that identifies the bottom of the GUI as the place to display the tab markers. (You can also pass either of SwingUtilities ' TOP, LEFT, or RIGHT constants.) After the tabbed pane is created, its addTab method is called to add a label to the first tab and a button to the second tab. Figure 16.23 shows the resulting GUI.

Figure 16.23. Swing's JTabbedPane class can help you manage screen space.

graphics/16fig23.gif

Version 1.3 of the Java 2 Platform Standard Edition adds a pair of methods to the JTabbedPane class, making it possible to associate ToolTips with individual tabs. (For more information, check out the tabbed pane section of the new Swing features in the SDK documentation.)

Option and Split Panes

Two panes have not yet been explored: option and split. An option pane (created from the JOptionPane class) is used to activate a standard dialog box (such as confirmation, input, and message dialog boxes) by using one of its class methods. For example, the following method displays an error message dialog box (as opposed to warning message or question message dialog boxes):

 JOptionPane.showMessageDialog (null, "File deleted", "Error",                                JOptionPane.ERROR_MESSAGE); 

The null argument indicates that the dialog box is independent of a parent component. The "File deleted" string serves as the message dialog box's message. The "Error" string appears in the dialog box's title bar. Finally, the ERROR_MESSAGE constant (one of several) identifies the type of message dialog box.

Tip

Each of JOptionPane 's showInputDialog methods returns a null reference when the user selects the dialog's Cancel button.


A split pane (created from the JSplitPane class) is used to graphically divide a pair of components. For more information (and examples), consult the SDK documentation and the Java Tutorial.

Exploring Layout Managers

There are no restrictions on using the AWT's layout managers with your Swing programs. However, you might prefer using Swing's box layout manager. Box offers features that are similar to the AWT's more complex grid-bag layout manager, but is much easier to userespects a component's maximum size and alignment. Furthermore, Swing simplifies the manner in which a component's minimum, preferred, and maximum sizes (along with alignment) are specified.

Boxes

The box layout manager allows multiple components to be laid out horizontally or vertically. When its container is resized, box layout causes horizontally laid out components to stay in a horizontal row and vertically laid out components to stay in a vertical column.

Objects created from the BoxLayout class (located in the javax.swing package) represent box layout managers. Call the BoxLayout (Container target, int axis) constructor to create a box that lays out its components (in the target container) along a major axis (specified by the axis argument). Either BoxLayout.X_AXIS (for laying out components in a horizontal row) or BoxLayout.Y_AXIS (for laying out components in a vertical column) can be specified as the axis argument. For example, the following code fragment creates a box layout manager that lays out its components in a vertical column:

 JPanel jp = new JPanel (); jp.setLayout (new BoxLayout (jp, BoxLayout.Y_AXIS)); 

To see what this looks like, check out Figure 16.24. This figure shows the output from the BoxLayoutDemo1 application. (This application's source code is included with the rest of the book's source code).

Figure 16.24. Swing's box layout manager can lay out components in a vertical column.

graphics/16fig24.gif

Swing provides a convenient Box class that creates containers with a box layout manager. To create this container, call the Box (int axis) constructor and identify the major axis via the axis argument. This is demonstrated in the following code fragment:

 Box b = new Box (BoxLayout.X_AXIS); 

Box provides several methods for precisely controlling alignment. These methods create invisible components, known as struts, glues, and rigid areas.

A strut is a fixed-width invisible component that forces a certain amount of space between two components. Call createVerticalStrut to specify the number of empty pixels that separate two vertical components, or createHorizontalStrut to specify the number of empty pixels that separate two horizontal components. The following code fragment uses createHorizontalStrut to reserve 10 pixels of empty space between two horizontal components (and assumes that the previous code fragment was just executed):

 b.add (new JLabel ("First")); b.add (Box.createHorizontalStrut (10)); b.add (new JLabel ("Second")); 

A glue is an invisible component that expands to fill space between other components. It's typically used in a horizontal box in which components have a maximum width, or a vertical box in which components have a maximum height. Call createHorizontalGlue or createVerticalGlue to create a glue between two horizontal components or two vertical components. The following code fragment adds horizontal glue between two labels:

 b.add (new JLabel ("First")); b.add (Box.createHorizontalGlue ()); b.add (new JLabel ("Second")); 

Finally, a rigid area is an invisible component with a specific width and height. You can depend on this component "wasting" a precise number of pixels in your container. The createRigidArea method is called with a Dimension argument that specifies the size of a rigid area. The region that's identified by this argument will consist of empty pixels. The following code fragment demonstrates the use of this method to reserve a rigid area of 10 20 pixels:

 b.add (Box.createRigidArea (new Dimension (10, 20)); 

To learn more about glues and struts, check out the source code to BoxLayoutDemo2, in Listing 16.18.

Listing 16.18 The BoxLayoutDemo2 Application Source Code
 // BoxLayoutDemo2.java import javax.swing.*; import java.awt.*; import java.awt.event.*; class BoxLayoutDemo2 extends JFrame {    BoxLayoutDemo2 (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       Box box = Box.createHorizontalBox ();       box.add (Box.createHorizontalStrut (10));       JButton jb = new JButton ("Left button");       box.add (jb);       box.add (Box.createHorizontalGlue ());       jb = new JButton ("Right button");       box.add (jb);       box.add (Box.createHorizontalStrut (10));       getContentPane ().add (box);       setSize (300, 100);       setVisible (true);    }    public static void main (String [] args)    {       new BoxLayoutDemo2 ("Box Layout Demo2");    } } 

BoxLayoutDemo2 creates a horizontal box layout. The first and last components are invisible struts that are 10 pixels wide. They reserve margins around the left and right edges of the container. A glue component is placed between both labels, so extra space will appear between the labels, and not to the right of the right-most label.

Sizing and Alignment

To determine a component's minimum and preferred sizes, the AWT's layout managers call a component's getMinimumSize and getPreferredSize methods. The only way the AWT lets you specify new minimum and preferred sizes is to subclass a component's class and override these methods. However, Swing's JComponent class provides setMinimumSize and setPreferredSize methods to achieve the same goal ”without subclassing.

If you study the Component class, you'll discover getMaximumSize. This method returns the maximum size of a component. However, it was never called by the AWT's layout managers, so getMaximumSize was never used. However, the box layout manager supports maximum sizes, and JComponent provides a more convenient setMaximumSize method to specify this size.

Figure 16.24 appears to violate what the SDK documentation says about BoxLayout. To quote from this documentation, " BoxLayout attempts to make all components in the column as wide as the widest component." However, when you look at this figure, these components have different sizes. What's going on?

BoxLayout will only draw a component up to its maximum size, and a component's maximum size is generally the same as its preferred size. Therefore, if you want the component to be drawn wider, you must change this maximum size. This task is accomplished by calling setMaximumSize. Figure 16.25 shows the resulting GUI from the BoxLayoutDemo3 application that does just this. ( BoxLayoutDemo3 's source code is included with the rest of this book's source code.)

Figure 16.25. Swing's box layout manager stretches its components to their maximum sizes.

graphics/16fig25.gif

While studying Component, you'll probably encounter getAlignmentX and getAlignmentY. These methods return alignment data (as floating point values between 0.0 and 1.0) that indicate how to align components (left, right, centered, or somewhere in between). However, the AWT's layout managers do not use this information, although it's used by box layout to more accurately position components. To eliminate the need for subclassing, JComponent provides the setAlignmentX and setAlignmentY methods to specify this information.

Suppose you want to align three vertical components with the right-hand side of a container. You would call setAlignmentX with a RIGHT_ALIGNMENT argument for each component. (The RIGHT_ALIGNMENT constant is inherited from the Component class.) Figure 16.26 shows the resulting alignment. (The source code to the BoxLayoutDemo4 application that was used to produce this illustration is included with the rest of this book's source code.)

Figure 16.26. Swing's box layout manager can align components in a container.

graphics/16fig26.gif

Caution

All components in a column should be aligned using the same alignment. Otherwise, weird looking GUIs will result. For example, if a column is to consist of three right-aligned components, call setAlignmentX (RIGHT_ALIGNMENT) for each component.


Validation, Invalidation , and Revalidation

When a developer begins writing AWT and Swing programs, she is often faced with trying to figure out why the GUI isn't being properly laid out. For example, an AWT label is assigned text (by calling its setText method) and this text is truncated. To understand this situation, it's important to have a good understanding of how validation, invalidation, and revalidation work.

In a nutshell , validation is the process of making sure that all components are properly sized and laid out in their containers. In contrast, invalidation is the process of marking a component and/or a container as being invalid in response to modifying a component (by changing its font, text, and so on). Finally, revalidation is a concept (introduced by Swing) that is similar to validation.

Validation is achieved by calling validate, and invalidation is a achieved by calling invalidate. (As you learned in Chapter 14, the Component and Container classes have their own versions of these methods.) Finally, revalidation is achieved by calling JComponent 's revalidate method. Understanding how these methods work will help you understand why your GUIs aren't always laid out according to your desires. You first need to consider validate and invalidate by investigating AWT code. Then, you will move on to Swing code, where revalidate can be explored.

Validation and invalidation will be explored by studying an AWT application called ValidationDemo1. This application presents a GUI with a label and two buttons ” Change and Restore. Initially, this label displays Hello. If the Change button is clicked, the label displays Hello World. If Restore is clicked, the label reverts to displaying Hello. In either case, the following code fragment (taken from ValidationDemo1 ) is executed (it assumes the existence of a Label object called l ):

 System.out.println ("Label validity: " + l.isValid ()); System.out.println ("Label's container validity: " + isValid ()); System.out.println ("Label's preferred size: " + l.getPreferredSize ()); System.out.println ("Label's minimum size: " + l.getMinimumSize ()); System.out.println ("Label's maximum size: " + l.getMaximumSize ()); if (e.getActionCommand ().equals ("Change"))     l.setText ("Hello World"); else     l.setText ("Hello"); if (option != NOTHING) {     setSize (getPreferredSize ());    if (option == SIZE_AND_VALIDATE)         validate (); } System.out.println ("Label validity: " + l.isValid ()); System.out.println ("Label's container validity: " + isValid ()); System.out.println ("Label's preferred size: " + l.getPreferredSize ()); System.out.println ("Label's minimum size: " + l.getMinimumSize ()); System.out.println ("Label's maximum size: " + l.getMaximumSize ());] 

Before studying this code fragment, take a look at Figure 16.27. This figure displays three windows. The top window appears when the program is run without any command arguments. It displays the GUI after the Change button is clicked. Notice that the label's text is truncated. The middle window appears when the program is run with a command argument of S (for sizing). As you can see, the window has been resized. However, the button's text is still truncated. Finally, the bottom window appears when the program is run with a command argument of V (for sizing and validation). In addition to the window being resized, validation ensures that this text is not truncated.

Figure 16.27. ValidationDemo1 's GUI is shown when no arguments (top), an S argument, and a V argument (bottom) are specified.

graphics/16fig27.gif

Whether or not command arguments are specified, assume that a user has just clicked the Change button (for the first time and has not clicked the Restore button), and that the preceding code fragment is being executed.

The System.out.println method calls output information about the label and its parent container. When this code is first executed, the isValid method calls return a Boolean true value. (At this point, both the label and its container are in a valid state.) Furthermore, the label's preferred and minimum sizes are equal, and the maximum size (which is not used by the AWT, but is included for completeness) suggests that the component can grow to 32,767 by 32,767 pixels (when run under Windows 98).

Because Change was clicked, setText ("Hello World") (in the Label class) is called. (Don't forget, Hello is currently being displayed by the label.)

The setText method assigns its text argument to the label's peer (if it exists, which is the case because the GUI is visible when this method is called.) (The peer has the capability to increase its preferred size by a small margin. However, this isn't enough to prevent text from being truncated at the peer's right edge.) If the label is valid (which is currently the case), its inherited invalidate method (from the Component class) is called.

In response, invalidate sets an internal Boolean valid flag to false, nullifies the label's minimum and preferred sizes, and (assuming there is a parent container and that this container is valid ”which is the case on both counts), calls the parent container's invalidate method. (Containers inherit the invalidate method from the Container class. Therefore, it is Container 's invalidate method which is just being called.)

The container's invalidate method checks to see if the current layout manager is an instance of a class that implements the LayoutManager2 interface. If this is the case, the layout manager's invalidateLayout method is called to clear out any cached layout information. Upon return, the component layer's invalidate method is called.

Note

Remember: An AWT container is created from the Container class, and this class is a subclass of the Component class. As a result, a Container has a Container class outer layer and a Component class inner layer.


The component layer's invalidate method sets the Boolean valid flag to false (for the label's parent container) and nullifies its minimum and preferred sizes. If the label's parent container has a parent container (and this parent container is valid, which would be the case when the Change button is clicked for the first time), this new parent container's invalidate method is called and the process continues. Otherwise, invalidate exits, the stack is rewound, and execution picks up right after the setText method call.

What happens next depends on the value of the option variable. This variable is assigned a value to indicate whether code that changes the container's size and validates the container executes. It gets its value from parsing a command argument to the ValidationDemo1 program.

Assuming no command arguments were passed to ValidationDemo1, option has a default value of NOTHING. As a result, the container's size and validity are not changed. Therefore, the second group of System.out.println method calls will report the label and container to be invalid. (If you resize the container, both the container and label will be validated . As a result, the first group of System.out.println method calls will report both the label and container to be valid when the Change button is clicked for a second time.) Note that the label's peer is still showing truncated text.

Assuming the S command argument was passed (as in java ValidationDemo1 S ), option is assigned the value SIZE_ONLY. As a result, setSize (getPreferredSize ()); is called.

The container's getPreferredSize method is called to return its preferred size. The return value should be null, because the previous invalidate method call (resulting from the setText method call) nullified this size. However, this isn't the case.

The container's getPreferredSize method calls an internal preferredSize method. This latter method discovers that the preferred size is null. As a result, it does one of two things. If there is a layout manager ”you haven't called setLayout (null) ” for this container, the layout manager's preferredLayoutSize method is called to return the layout size. Otherwise, the component layer's preferredSize method is called to return the preferred size. Because ValidationDemo1 has not removed any layout managers, preferredLayoutSize is called with a reference to the container as its argument.

The preferredLayoutSize method calculates the preferred size of its container. To carry out this task, it calls the getPreferredSize method for each of the container's components. (In this example, you have two buttons and one label contained in the container. Also, it is the getPreferredSize method from the Component class ”and not the Container class ”that is called.)

For each button, the getPreferredSize method returns the previous preferred size for these components, because they haven't changed their sizes. However, when called for the label, getPreferredSize "notices" that the preferred size is null. As a result, it returns the peer's preferred size (because there is a peer ”the getMinimumSize method would be called if there wasn't a peer).

When the peer's text was set during the earlier call to setText, it was able to slightly increase the width of the label peer. As a result, the width is a little larger than it was before the setText method call. Eventually, through stack rewinding, the original getPreferredSize method call returns this value. At this point, the container's setSize method is called.

The container inherits its setSize method from the Component class. When called, this method calls Component 's resize method ”the overloaded version that takes a Dimension argument. This resize method calls setSize (d.width, d.height) ” assuming the Dimension argument is called d. Finally, this setSize method calls a second resize method that takes two int argument (for the width and height).

The resize method calls the setBounds (int x, int y, int width, int height) method. (The x and y arguments identify the upper-left corner of the container. They can be ignored in this scenario, because you aren't moving ValidationDemo1 's main window around the screen.) In turn, this method calls the reshape method with the same four arguments. (You're getting to the good stuff.)

The reshape method sets a Boolean resized flag to true, if the container's new width and height differ from the old width and height. Obviously, this flag will be set to true, because the previous getPreferredSize method call returned the peer's new size (after setText was called), which differs from the old size.

The reshape method ends up calling the peer's setBounds method, the component layer's invalidate method, the parent container's invalidate method (if there is a parent), and then dispatches a component resized event. When this method exits, and the stack unwinds to a return from the setSize method call, the container is displayed at the new size (thanks to the call to the peer's setBounds method). Note that the label's peer is still showing truncated text.

Assuming the V command argument was passed (as in java ValidationDemo1 V ), option is assigned the value SIZE_AND_VALIDATE ). As a result, the container's validate method is called.

In response, validate calls the validateTree helper method to recursively lay out (by calling the container's overridden doLayout method) and validate each contained container and component. (A component is validated by calling its validate method, inherited from Component. ) The validateTree method sets the valid flag to true for each container, and the component's validate method sets the valid flag to true for each component. ( Component 's isValid method returns the value of this flag.)

The doLayout method calls the layout helper method, and this method calls the current layout manager's layoutContainer method (assuming the container has a layout manager.) As a result, the label's peer will show nontruncated text.

Although long-winded, you have just been taken on a condensed journey through the AWT's source code. Hopefully, this will help you come to terms with what is happening behind the scenes when a GUI is laid out.

Tip

The pack method is a wrapper for setSize (getPreferredSize ()); validate ();. Because this method originates in the AWT's Window class, is inherited by the AWT's Frame class ”because Frame is derived from Window ” and because JFrame is derived from Frame, you can always call pack in your Swing applications.


Now that validation and invalidation have been discussed, it's time to focus on Swing's revalidation concept. Revalidation involves automatic calls to JComponent 's revalidate method when something happens to a Swing component that affects it size (such as setting its text). When this occurs, revalidate invalidates the component (or container), by calling Container 's invalidate method ( JComponent is derived from Container ), and queues a validation request for a later time by calling Swing's repaint manager's addInvalidComponent method and scheduling a subsequent call to the repaint manager's validateInvalidComponents method. (The repaint manager manages repaint requests and is created from Swing's RepaintManager class.)

The ValidationDemo2 application (identical to the ValidationDemo1 application except that Swing is being used) demonstrates revalidation. (As with ValidationDemo1, ValidationDemo2 's source code is included with the rest of the book's source code.) Figure 16.28 displays ValidationDemo2 's windows. (These windows are the Swing equivalents of the three windows shown in Figure 16.27, and were generated in the same manner.)

Figure 16.28. ValidationDemo2 's GUI is shown when no arguments (top), an S argument, and a V argument (bottom) are specified.

graphics/16fig28.gif

When you study Figure 16.28, you'll notice that Hello World is not truncated. It's not truncated because JLabel 's setText method calls revalidate to validate the label. In turn, this method calls the repaint manager's addInvalidateComponent method, and addInvalidateComponent searches for the highest JComponent container whose isValidateRoot method returns true (which would be the root pane in this example). This root component is used by validateInvalidComponents to call the validate method, starting with the root container and working its way down to the lowest container (with its components), to ensure that all containers and components are resized. However, this does not apply to the JFrame container that contains everything else. As a result, the main window is not resized during revalidation.

Tip

The only way to understand what goes on with Java and the JFC (in general), and concepts such as validation (in particular), is to devise example programs and walk through Java's source code (like you've just done). You might want to consider setting aside some time (now and then) for doing just that. If you do, your skills will definitely increase and your talents will be in demand.


   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

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