Library Zone Articles
External Articles
Byte Size

Discovery Zone Catalogue
Diary
Links
Bookstore
Interactive Zone Ask the Gurus
Discussion Groups
Newsletters
Feedback
Etc Cartoons
Humour
COMpetition
Advertising ASP Web Ring ASP Web Ring
Power your site with idr newswire
The Developer's Resource & Community Site
COM XML ASP Java & Misc. NEW: VS.NET
International This Week Forums Author Central Find a Job
Buy the book: Java Foundation Classes in a Nutshell

Java Foundation Classes in a Nutshell: Swing Programming Topics, page 3

Reproduced with kind permision of O'Reilly & Associates: www.oreilly.com

3.17 JColorChooser

Just as JFileChooser allows the user to choose a file, javax.swing.JColorChooser allows the user to choose a color. Figure 3.6 shows a JColorChooser dialog. You can embed a JColorChooser component directly in your application or in a custom dialog box, but the most common way to use it is to simply call the static showDialog() method:

Color c = JColorChooser.showDialog(contentpane,    // Dialog appears over this
                                   "Pick a Color", // Dialog title 
                                   Color.white);   // Default color selection

Figure 3.6: A JColorChooser dialog

As you can see from Figure 3.6, JColorChooser displays a color selection pane and a color preview pane. The selection pane is actually a JTabbedPane that allows colors to be selected in three different ways. The Swatches pane lets the user select a color from a palette of color swatches. With the RGB pane, the user picks a color by specifying the red, green, and blue components of the color, while with the HSV pane, the user specifies the hue, saturation, and value components of the color.

Instead of displaying a generic JColorChooser with the static showDialog() method, you can create your own instance of the JColorChooser class. You can then set properties on the color chooser object and display it in any way you want. The static JColorChooser.createDialog() method is useful here. It creates a dialog box to hold your JColorChooser pane and allows you to specify two ActionListener objects that are invoked in response to the OK and Cancel buttons in the dialog box.

You can customize a JColorChooser by adding a new color selection panel or a new color preview panel. To add a new color selection panel (for example, a panel that allows the user to select a grayscale color or a CMYK color), implement a subclass of AbstractColorChooserPanel (from the javax.swing.colorchooser package) and pass it to the addChooserPanel() method of your JColorChooser. Your custom panel contains a ColorSelectionModel that serves as the interface between your pane and the JColorChooser. All your pane needs to do is update the selected color of its ColorSelectionModel ( ColorSelectionModel is also part of the javax.swing.colorchooser package).

You can use any JComponent as a custom preview panel for your JColorChooser. Simply pass the component to setPreviewPanel(). The preview component has to track the currently selected color by listening for ChangeEvent events generated by the ColorSelectionModel of the JColorChooser.

3.18 Menus

In Swing, menu bars, menu panes, and menu items are components, just like all other Swing components. JMenuBar is a container designed to hold JMenu objects. JMenu is a container designed to hold JMenuItem objects and other JMenu objects (as submenus). Working with menus is not exactly the same as working with other types of components, however, and Example 3.1 shows a simple example of creating pull-down and pop-up menus.

Example 3.1: Creating Pull-Down and Pop-Up Menus in Swing

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class MenuDemo {
  public static void main(String[] args) {
    // Create a window for this demo
    JFrame frame = new JFrame("Menu Demo");
    JPanel panel = new JPanel();
    frame.getContentPane().add(panel, "Center");

    // Create an action listener for the menu items we will create
    // The MenuItemActionListener class is defined below
    ActionListener listener = new MenuItemActionListener(panel);

    // Create some menu panes, and fill them with menu items
    // The menuItem() method is important.  It is defined below.
    JMenu file = new JMenu("File");
    file.setMnemonic('F');
    file.add(menuItem("New", listener, "new", 'N', KeyEvent.VK_N));
    file.add(menuItem("Open...", listener, "open", 'O', KeyEvent.VK_O));
    file.add(menuItem("Save", listener, "save", 'S', KeyEvent.VK_S));
    file.add(menuItem("Save As...", listener, "saveas", 'A', KeyEvent.VK_A));

    JMenu edit = new JMenu("Edit");
    edit.setMnemonic('E');
    edit.add(menuItem("Cut", listener, "cut", 0, KeyEvent.VK_X));
    edit.add(menuItem("Copy", listener, "copy", 'C', KeyEvent.VK_C));
    edit.add(menuItem("Paste", listener, "paste", 0, KeyEvent.VK_V));

    // Create a menu bar and add these panes to it.
    JMenuBar menubar = new JMenuBar();
    menubar.add(file);
    menubar.add(edit);

    // Add menu bar to the main window.  Note special method to add menu bars.
    frame.setJMenuBar(menubar); 

    // Now create a popup menu and add the some stuff to it
    final JPopupMenu popup = new JPopupMenu();
    popup.add(menuItem("Open...", listener, "open", 0, 0));
    popup.addSeparator();                // Add a separator between items
    JMenu colors = new JMenu("Colors");  // Create a submenu
    popup.add(colors);                   // and add it to the popup menu
    // Now fill the submenu with mutually exclusive radio buttons
    ButtonGroup colorgroup = new ButtonGroup();
    colors.add(radioItem("Red", listener, "color(red)", colorgroup));
    colors.add(radioItem("Green", listener, "color(green)", colorgroup));
    colors.add(radioItem("Blue", listener, "color(blue)", colorgroup));

    // Arrange to display the popup menu when the user clicks in the window
    panel.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
	// Check whether this is the right type of event to pop up a popup
	// menu on this platform.  Usually checks for right button down.
	if (e.isPopupTrigger()) 
	  popup.show((Component)e.getSource(), e.getX(), e.getY());
      }
    });

    // Finally, make our main window appear
    frame.setSize(450, 300);
    frame.setVisible(true);
  }

  // A convenience method for creating menu items
  public static JMenuItem menuItem(String label, 
				   ActionListener listener, String command, 
				   int mnemonic, int acceleratorKey) {
    JMenuItem item = new JMenuItem(label);
    item.addActionListener(listener);
    item.setActionCommand(command);
    if (mnemonic != 0) item.setMnemonic((char) mnemonic);
    if (acceleratorKey != 0) 
      item.setAccelerator(KeyStroke.getKeyStroke(acceleratorKey, 
						 java.awt.Event.CTRL_MASK));
    return item;
  }

  // A convenience method for creating radio button menu items
  public static JMenuItem radioItem(String label, ActionListener listener, 
				    String command, ButtonGroup mutExGroup) {
    JMenuItem item = new JRadioButtonMenuItem(label);
    item.addActionListener(listener);
    item.setActionCommand(command);
    mutExGroup.add(item);
    return item;
  }

  // An event listener class used with the menu items created above
  // For this demo, it just displays a dialog box when an item is selected
  public static class MenuItemActionListener implements ActionListener {
    Component parent;
    public MenuItemActionListener(Component parent) { this.parent = parent; }
    public void actionPerformed(ActionEvent e) {
      JMenuItem item = (JMenuItem) e.getSource();
      String cmd = item.getActionCommand();
      JOptionPane.showMessageDialog(parent, cmd + " was selected.");
    }
  }
}

3.19 JTree and TreeModel

The javax.swing.JTree class is a powerful Swing component for displaying tree-structured data. Like all Swing components, JTree relies on a separate model object to hold and represent the data that it displays. Most Swing components create this model object automatically, and you never need to work with it explicitly. The JTree component, however, displays data that is much more complex than a typical Swing component. When you are working with a JTree, you must create a model object that implements the javax.swing.tree.TreeModel interface.

One approach is to use the DefaultTreeModel class, which implements the TreeModel interface using the TreeNode and MutableTreeNode interfaces (all defined in javax.swing.tree). To use DefaultTreeModel, you must implement your hierarchical data structures so that each element of the tree implements the TreeNode or MutableTreeNode interface. Now you can create a DefaultTreeModel object simply by passing the root TreeNode of your tree to a DefaultTreeModel constructor. Then you create a JTree component to display your tree simply by passing the DefaultTreeModel to the setModel() method of the JTree.

Sometimes, however, you do not have the luxury of designing the data structures used to represent your tree, so implementing the TreeNode interface is simply not an option. In this case, you can implement the TreeModel interface directly. The resulting TreeModel object serves as the interface between your data and the JTree component that displays the data. Your TreeModel implementation provides the methods that allow the JTree component to traverse the nodes of your tree, regardless of the actual representation of the tree data.

Example 3.2 shows a program that implements the TreeModel interface to represent the hierarchical structure of the filesystem, thereby allowing the file and directory tree to be displayed in a JTree component. Notice how a relatively simple implementation of TreeModel enables the powerful tree- browsing capabilities shown in Figure 3.7.

Figure 3.7: The JTree component

Example 3.2: Using JTree and TreeModel

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.io.File;

public class FileTreeDemo {
  public static void main(String[] args) {
    // Figure out where in the filesystem to start displaying
    File root;
    if (args.length > 0) root = new File(args[0]);
    else root = new File(System.getProperty("user.home"));

    // Create a TreeModel object to represent our tree of files
    FileTreeModel model = new FileTreeModel(root);

    // Create a JTree and tell it to display our model
    JTree tree = new JTree();
    tree.setModel(model);

    // The JTree can get big, so allow it to scroll
    JScrollPane scrollpane = new JScrollPane(tree);
    
    // Display it all in a window and make the window appear
    JFrame frame = new JFrame("FileTreeDemo");
    frame.getContentPane().add(scrollpane, "Center");
    frame.setSize(400,600);
    frame.setVisible(true);
  }
}

/**
 * The methods in this class allow the JTree component to traverse
 * the file system tree and display the files and directories.
 **/
class FileTreeModel implements TreeModel {
  // We specify the root directory when we create the model.
  protected File root;
  public FileTreeModel(File root) { this.root = root; }

  // The model knows how to return the root object of the tree
  public Object getRoot() { return root; }

  // Tell JTree whether an object in the tree is a leaf
  public boolean isLeaf(Object node) {  return ((File)node).isFile(); }

  // Tell JTree how many children a node has
  public int getChildCount(Object parent) {
    String[] children = ((File)parent).list();
    if (children == null) return 0;
    return children.length;
  }

  // Fetch any numbered child of a node for the JTree.
  // Our model returns File objects for all nodes in the tree.  The
  // JTree displays these by calling the File.toString() method.
  public Object getChild(Object parent, int index) {
    String[] children = ((File)parent).list();
    if ((children == null) || (index >= children.length)) return null;
    return new File((File) parent, children[index]);
  }

  // Figure out a child's position in its parent node.
  public int getIndexOfChild(Object parent, Object child) {
    String[] children = ((File)parent).list();
    if (children == null) return -1;
    String childname = ((File)child).getName();
    for(int i = 0; i < children.length; i++) {
      if (childname.equals(children[i])) return i;
    }
    return -1;
  }

  // This method is invoked by the JTree only for editable trees.  
  // This TreeModel does not allow editing, so we do not implement 
  // this method.  The JTree editable property is false by default.
  public void valueForPathChanged(TreePath path, Object newvalue) {}

  // Since this is not an editable tree model, we never fire any events,
  // so we don't actually have to keep track of interested listeners
  public void addTreeModelListener(TreeModelListener l) {}
  public void removeTreeModelListener(TreeModelListener l) {}
}

3.20 JTable and TableModel

javax.swing.JTable is another powerful Swing component for displaying complex data structures. Like JTree, JTable relies on a separate model object to hold and represent the data it displays and has its own package of helper classes, javax.swing.table. This package contains the TableModel interface and its default implementations, AbstractTableModel and DefaultTableModel.

If your table data is tidily organized, it is easy to use JTable without worrying about the TableModel. If your data is an array of rows, where each row is an array of objects, you can just pass this Object[][] directly to the JTable constructor. If you want, you can also specify an optional array of column names. This is all you need to do: the JTable does the rest. This technique also works if your data is stored in a Vector of rows, where each row is itself a Vector.

Often, however, your data is not as regular as that. When you want to display a tabular view of data that is not, by nature, tabular, you must implement the TableModel interface (or, more likely, subclass the AbstractTableModel class). The job of this TableModel implementation is to serve as the interface between your data, which is not neatly organized into a table, and the JTable object, which wants to display a table. In other words, your TableModel presents a neat tabular view of your data, regardless of how the data is organized underneath.

Example 3.3 shows how this can be done. Given a File object that represents a directory in the filesystem, this example displays the contents of that directory in tabular form, as shown in Figure 3.8. Once again, notice how a relatively simple TableModel implementation enables the use of the powerful table-display capabilities of the JTable component.

Figure 3.8: The JTable component

Example 3.3: Using JTable and TableModel

import javax.swing.*;
import javax.swing.table.*;
import java.io.File;
import java.util.Date;

public class FileTableDemo {
  public static void main(String[] args) {
    // Figure out what directory to display
    File dir;
    if (args.length > 0) dir = new File(args[0]);
    else dir = new File(System.getProperty("user.home"));

    // Create a TableModel object to represent the contents of the directory
    FileTableModel model = new FileTableModel(dir);

    // Create a JTable and tell it to display our model
    JTable table = new JTable(model);

    // Display it all in a scrolling window and make the window appear
    JFrame frame = new JFrame("FileTableDemo");
    frame.getContentPane().add(new JScrollPane(table), "Center");
    frame.setSize(600, 400);
    frame.setVisible(true);
  }
}

/**
 * The methods in this class allow the JTable component to get
 * and display data about the files in a specified directory.
 * It represents a table with six columns: filename, size, modification date, 
 * plus three columns for flags: directory, readable, writable.
 **/
class FileTableModel extends AbstractTableModel {
  protected File dir;
  protected String[] filenames;

  protected String[] columnNames = new String[] {
    "name", "size", "last modified", "directory?", "readable?", "writable?"
  };

  protected Class[] columnClasses = new Class[] { 
    String.class, Long.class, Date.class, 
      Boolean.class, Boolean.class, Boolean.class
  };

  // This table model works for any one given directory
  public FileTableModel(File dir) { 
    this.dir = dir; 
    this.filenames = dir.list();  // Store a list of files in the directory
  }

  // These are easy methods
  public int getColumnCount() { return 6; }  // A constant for this model
  public int getRowCount() { return filenames.length; }  // # of files in dir

  // Information about each column
  public String getColumnName(int col) { return columnNames[col]; }
  public Class getColumnClass(int col) { return columnClasses[col]; }

  // The method that must actually return the value of each cell
  public Object getValueAt(int row, int col) {
    File f = new File(dir, filenames[row]);
    switch(col) {
    case 0: return filenames[row];
    case 1: return new Long(f.length());
    case 2: return new Date(f.lastModified());
    case 3: return f.isDirectory() ? Boolean.TRUE : Boolean.FALSE;
    case 4: return f.canRead() ? Boolean.TRUE : Boolean.FALSE;
    case 5: return f.canWrite() ? Boolean.TRUE : Boolean.FALSE;
    default: return null;
    }
  }
}

3.21 JTextComponent and HTML Text Display

The most complex component in all of Swing is the JTextComponent, which is a powerful editor. It is part of the javax.swing.text package and generally is not used directly. Instead, you typically use one of its subclasses, such as JTextField, JPasswordField, JTextArea, or JEditorPane. The first three of these components are straightforward. They are for the entry of a single line of text, secret text such as a password, and simple, unformatted, multiline text, respectively.

It is the JEditorPane component that really makes use of the full power of JTextComponent. JEditorPane supports the display and editing of complex formatted text. In conjunction with the classes in the javax.swing.text.html and javax.swing.text.rtf packages, JEditorPane can display and edit HTML and RTF documents. The ability to display formatted text so easily is a very powerful feature. For example, the ability to display HTML documents makes it simple for a Swing application to add online help based on an HTML version of the application's user manual. Furthermore, formatted text is a professional-looking way for an application to display its output to the user.

Because HTML has become so ubiquitous, we'll focus on the display of HTML documents with JEditorPane, There are several different ways to get a JEditorPane to display an HTML document. If the desired document is available on the network, the easiest way to display it is simply to pass an appropriate java.net.URL object to the setPage() method of JEditorPane. setPage() determines the data type of the document and, assuming it is an HTML document, loads it and displays it as such. For example:

editor.setPage(new java.net.URL("https://www.my.com/product/help.htm"));

If the document you want to display is in a local file or is available from some kind of InputStream, you can display it by passing the appropriate stream to the read() method of JEditorPane. The second argument to this method should be null. For example:

InputStream in = new FileInputStream("help.htm");
editor.read(in, null);

Yet another way to display text in a JEditorPane is to pass the text to the setText() method. Before you do this, however, you must tell the editor what type of text to expect:

editor.setContentType("text/html");
editor.setText("<H1>Hello World!</H1>");
Calling setText() can be particularly useful when your application generates HTML text on the fly and wants to use a JEditorPane to display nicely formatted output to the user.

Example 3.4 shows one such use of the JEditorPane. This example is an alternative to Example 3.3: it displays the contents of a directory in tabular form but uses an HTML table instead of the JTable component. As a bonus, this example uses HTML hyperlinks to allow the user to browse from one directory to the next. (If you download and run the two examples, however, you'll probably notice that the JTable example is significantly faster, since it does not have to encode the directory contents into HTML and then parse that HTML into a table.) Figure 3.9 shows sample output from this example.

Figure 3.9: The JEditorPane component displaying an HTML table

Example 3.4: Dynamically Generated HTML in JEditorPane

import javax.swing.*;
import javax.swing.event.*;
import java.io.*;
import java.util.Date;

/**
 * This class implements a simple directory browser using the HTML
 * display capabilities of the JEditorPane component
 **/
public class FileTableHTML {
  public static void main(String[] args) throws IOException {
    // Get the name of the directory to display
    String dirname = (args.length>0)?args[0]:System.getProperty("user.home");

    // Create something to display it in
    final JEditorPane editor = new JEditorPane();
    editor.setEditable(false);               // we're browsing not editing
    editor.setContentType("text/html");      // must specify HTML text
    editor.setText(makeHTMLTable(dirname));  // specify the text to display
  
    // Set up the JEditorPane to handle clicks on hyperlinks
    editor.addHyperlinkListener(new HyperlinkListener() {
      public void hyperlinkUpdate(HyperlinkEvent e) {
	// Handle clicks; ignore mouseovers and other link-related events
	if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
	  // Get the HREF of the link and display it.
	  editor.setText(makeHTMLTable(e.getDescription()));
	}
      }
    });

    // Put the JEditorPane in a scrolling window and display it
    JFrame frame = new JFrame("FileTableHTML");
    frame.getContentPane().add(new JScrollPane(editor));
    frame.setSize(650, 500);
    frame.setVisible(true);
  }

  // This method returns an HTML table representing the specified directory
  public static String makeHTMLTable(String dirname) {
    // Look up the contents of the directory
    File dir = new File(dirname);
    String[] entries = dir.list();

    // Set up an output stream we can print the table to.
    // This is easier than concatenating strings all the time.
    StringWriter sout = new StringWriter();
    PrintWriter out = new PrintWriter(sout);
    
    // Print the directory name as the page title
    out.println("<H1>" + dirname + "</H1>");

    // Print an "up" link, unless we're already at the root
    String parent = dir.getParent();
    if ((parent != null) && (parent.length() > 0)) 
      out.println("<A href=\"" + parent + "\">Up to parent directory</A><P>");

    // Print out the table
    out.print("<TABLE BORDER=2 WIDTH=600><TR>");
    out.print("<TH>Name</TH><TH>Size</TH><TH>Modified</TH>");
    out.println("<TH>Readable?</TH><TH>Writable?</TH></TR>");
    for(int i=0; i < entries.length; i++) {
      File f = new File(dir, entries[i]);
      out.println("<TR><TD>" + 
		  (f.isDirectory() ?
		     "<a href=\""+f+"\">" + entries[i] + "</a>" : 
		     entries[i]) +
		  "</TD><TD>" + f.length() +
		  "</TD><TD>" + new Date(f.lastModified()) + 
		  "</TD><TD align=center>" + (f.canRead()?"x":" ") +
		  "</TD><TD align=center>" + (f.canWrite()?"x":" ") +
		  "</TD></TR>");
    }
    out.println("</TABLE>");
    out.close();

    // Get the string of HTML from the StringWriter and return it.
    return sout.toString();
  }
}

3.22 Pluggable Look-and-Feel

One of the unique features of Swing is its pluggable look-and-feel (PLAF) architecture, which allows a Swing application to change its entire appearance with one or two lines of code. The most common use of this feature is to give applications a choice between the native platform look-and-feel and a new platform-independent Java look-and-feel (also known as the Metal look-and-feel). Swing is distributed with three look-and-feels: Metal and two look-and-feels that mimic the appearance and behavior of the Windows and Motif (Unix/X) component toolkits. A look-and-feel that mimics the Macintosh platform is available as a separate download. While the Metal and Motif look-and-feels can be freely used, the Windows look-and-feel is restricted for use only on Windows platform - for copyright reasons, it does not run on any other operating system.

When a Swing application starts up, it reads the system property swing.defaultlaf to determine the classname of the default look-and-feel. In most Java installations, this property is set to the default Java look-and-feel, implemented by the class javax.swing.plaf.metal.MetalLookAndFeel. The end user can override this default by using the -D switch on the command line when invoking the Java interpreter. For example, to run a Swing application using the Motif look-and-feel, a user can type:

% java -Dswing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel app
If the user is using a Windows operating system, he can start the application using the Windows look-and-feel like this:
% java -Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel app

When you write a Swing application, you can explicitly set the look-and-feel that the application uses. To do this, simply call the static setLookAndFeel() method of the UIManager class and specify the classname of the desired look-and-feel implementation. To make this even easier, UIManager defines a static method that returns the classname of the default cross-platform look-and-feel (i.e., Metal) and another that returns the classname of the look-and-feel that mimics the native look-and-feel of the current platform. So, if you want your application to always look like a native application, you can simply include this line of code in your application, before it begins to create any GUI components:

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
Or, if you want to force the application to use the cross-platform look-and-feel, regardless of installation defaults and user preferences, you can use this line of code:
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());

Note that calling setLookAndFeel() like this overrides the value of the swing.defaultlaf property, if the end user has set one. Of course, the command-line syntax for setting that property is quite awkward and may be beyond the capabilities of many end users. An alternative is to implement command-line options in your own application that give the user a choice of look-and-feels. You might set a native look-and-feel if the user specifies a -nativelook flag on the command line, for example.

The easiest time to call the setLookAndFeel() method is at application start-up, before any Swing components have been created. It is also possible to change the look-and-feel of a running application, however. This means that you can allow the user to change the current look-and-feel through a preferences dialog box, if you are so inclined. When the user selects a new look-and-feel, you first call setLookAndFeel() to install the new look-and-feel, and then you have to notify all of the Swing components that a new look-and-feel is in effect and ask them to use it. Fortunately, there is a convenience method to do this. Your code might look like this:

// Set the new look-and-feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName);
// Tell all components from main JFrame on down that LAF has changed
SwingUtilities.updateComponentTreeUI(myframe);

A dialog that allows the user to change the currently installed look-and-feel of a running application should probably let the user choose among all the look-and-feels that are installed on the system. An application can find out the classnames and human-readable names of all the installed look-and-feels on a given system by calling the static getInstalledLookAndFeels() method of UIManager. In the implementation from Sun, this method returns either a default list of installed look-and-feels or a list obtained from the swing.properties file of the installation.

3.22.1 Using Themes with the Metal Look-and-Feel

You can customize the colors and fonts of the default Java look-and-feel by subclassing the DefaultMetalTheme class that appears in the javax.swing.plaf.metal package. When you create a custom subclass, you can specify the six different fonts and six different colors used by the Metal look-and-feel. For example, you might implement a large font theme for users who have difficulty reading the default fonts used by Metal.

If you are feeling brave and want to second-guess the skilled designers who put the Metal look-and-feel together, you can subclass the abstract MetalTheme class directly. This class defines many methods that return colors and fonts. All of these methods, however, are implemented in terms of the six basic font methods and six basic color methods of the DefaultMetalTheme class.

If you look at the DefaultMetalTheme API, you'll notice that the font and color methods do not return java.awt.Font and java.awt.Color objects as you would expect. Instead, they return FontUIResource and ColorUIResource objects. Both of these classes are part of the javax.swing.plaf package and are trivial subclasses of the more familiar Font and Color classes. The only thing these subclasses do is implement the UIResource interface. But UIResource is a marker interface, with no methods of its own. Thus, a FontUIResource is a Font object that also happens to implement UIResource. Similarly, a ColorUIResource is both a Color object and a UIResource object.

The currently installed look-and-feel assigns default values for many properties of Swing components. A look-and-feel implementation needs to be able to distinguish between default values it has specified and programmer-supplied property values. For this reason, all look-and-feel defaults, such as colors and fonts, must implement the UIResource marker interface. For our purposes here, you can subclass DefaultMetalTheme and use the FontUIResource and ColorUIResource classes exactly as you would use normal Font and Color resources.

Once you have created your own theme by subclassing MetalTheme or DefaultMetalTheme, you can install it with code like this:

MetalLookAndFeel.setCurrentTheme(new MyCustomTheme());
If you are changing the current theme after having already created Swing components, you also have to reinstall the MetalLookAndFeel and notify all the components of the change:
UIManager.setLookAndFeel(new MetalLookAndFeel());
SwingUtilities.updateComponentTreeUI(myRootFrame);

3.22.2 Auxiliary Look-and-Feels

If you've browsed the list of Swing packages, you've probably noticed javax.swing.plaf.multi. This is the multiplexing look-and-feel. It allows one or more auxiliary look-and-feels to be used in conjunction with a single primary look-and-feel. The multiplexing look-and-feel is automatically used by a Swing application if an auxiliary look-and-feel has been requested. An application can request an auxiliary look-and-feel by calling the static UIManager method addAuxiliaryLookAndFeel(), while an end user can do this by setting the swing.auxiliarylaf property on a Java command line.

The primary purpose of auxiliary look-and-feels is for accessibility. For example, a person with impaired vision might start up a Java application using the -Dswing.auxiliarylaf= option to specify that the application should load a screen-reader look-and-feel. Auxiliary look-and-feels can be used for other purposes as well, of course. You might use an auxiliary look-and-feel to add audio feedback to a user interface. Such a look-and-feel might produce an audible click when the user clicks on a JButton, for example.

Swing is not shipped with any predefined auxiliary look-and-feels. You can implement your own, of course, although explaining how to do so is beyond the scope of this book.

3.23 Accessibility

The term accessibility refers to the architectural features of Swing that allow Swing applications to interact with assistive technologies, such as a visual macro recorder that allows users to automate repetitive point-and-click tasks or a screen reader.

To enable accessibility, every Swing component implements the Accessible interface, which, like all accessibility-related classes, is part of the javax.accessibility package. This interface defines a single getAccessibleContext() method that returns an AccessibleContext object for the component. The methods of AccessibleContext export salient information about the component, such as a list of its accessible children and its name, purpose, and description. An assistive technology can use the tree of AccessibleContext objects to gather information about a GUI and assist the user in interacting with that GUI.

A number of the AccessibleContext methods return objects that implement specialized interfaces to return specific types of accessibility information. For example, if an accessible component represents a numeric value of some sort (say a JSlider), the getAccessibleValue() method of its AccessibleContext object returns an AccessibleValue object that provides more information about that value and allows the assistive technology to query and set the value.

The interfaces and classes of the javax.accessibility package provide methods that allow an assistive technology to "read" a GUI. Many of the methods defined by these interfaces duplicate functionality already provided by Swing components. The point, however, is that java.accessibility defines a standard API for interaction between any assistive technology and any accessible application. In other words, the accessibility API is not Swing specific. You can write JavaBeans and other custom components so that they support accessibility. If you do, these components automatically work with assistive technologies.

The details of the javax.accessibility package are of interest to programmers who are creating assistive technologies and developing accessible components or JavaBeans. Unfortunately, the details of these tasks are beyond the scope of this book.

Most of us are not developing assistive technologies and only rarely do we have to create accessible components. What we all want to do, however, is create accessible applications. Since all Swing components support accessibility, it is quite simple to create an accessible application with Swing. The key to supporting accessibility is providing the necessary information that allows an assistive technology to interpret your GUI for a user. The most commonly used example of an assistive technology is a screen reader for the vision impaired. A screen reader needs to be able to verbally describe a GUI to a user who cannot see it. In order to do this, it needs to have names and descriptions for all the critical components in your GUI.

The easiest way to assign a description to a component is to give it a tooltip. This way, your accessibility information also serves as context-sensitive help for novice users:

continue.setToolTipText("Click here to continue");
If, for some reason, you want to assign an accessible description to a component without giving it a tooltip, you can use code like this:
continue.getAccessibleContext().setAccessibleDescription("Continue button");

It is also helpful to assistive technologies if you provide names for your various components. A name should be a short human-readable string that uniquely identifies the component, at least within the current window or dialog box. Buttons, labels, menu items, and other components that display labels simply use those labels as their accessible names. Other components need to have names assigned. Here is one way to do that:

JTextField zipcode = new JTextField();
zipcode.getAccessibleContext().setAccessibleName("zipcode");

In a GUI, important components that do not display their own labels are often associated with JLabel components that serve to identify them. When this is the case, you can use the setLabelFor() method of JLabel to set the accessible name of the other component. The code might look like this:

JLabel zipcodeLabel = new JLabel("Zipcode");
JTextField zipcode = new JTextField();
zipcodeLabel.setLabelFor(zipcode);

By taking the simple step of assigning names and descriptions to your GUI components, you ensure that your application can be interpreted by assistive technologies and successfully used by all users.

3.24 Custom Components

We'll conclude this survey of Swing features with a quick look at what it takes to write a custom Swing component. Creating a custom component is a matter of subclassing an existing component and adding the new functionality you desire. Sometimes this is a simple job of adding a minor new feature to an existing component. At other times, you may want to create an entirely new component from scratch. In this case, you'll probably be subclassing JComponent, which is a bit more complicated. The following sections briefly explain the various things you'll need to consider when creating such a custom component. The best way to learn to write your own Swing-style components is to study the source code of Swing components, and since Sun makes this source code freely available, I encourage you to examine it.

3.24.1 Properties

You need to decide what properties you want your component to export and define accessor methods that allow them to be set and queried. If your component represents or displays some kind of nontrivial data structure, consider representing the data in a separate model object. Define an interface for the model and a default implementation of the interface.

If you think that other objects may be interested in property changes on your component, have the set methods for those properties generate the events PropertyChangeEvent or ChangeEvent and include appropriate event listener registration methods in your component. This kind of notification is often important if you follow the Swing architecture and divide the functionality of your component among a component object, a model object, and a UI delegate object.

When a property is set on your component, the component may need to be redrawn or resized as a result. You must keep this in mind when you write the property accessor methods for your component. For example, if you define a setColor() method, this method should call repaint() to request that the component be repainted. (Painting the component is a separate topic that is discussed later.) If you define a setFont() method and a change in font size causes the component to require more (or less) space on the screen, you should call revalidate() to request a relayout of the GUI. Note that the repaint() and revalidate() methods add a repaint or relayout request to a queue and return right away. Therefore, you may call these methods freely without fear of inefficiency.

3.24.2 Events

You need to decide what kind of events your component generates. You can reuse existing event and listener classes, if they suit your purposes, or you can define your own. Add event listener registration and deregistration methods in your component. You need to keep track of the registered listeners, and you may find the javax.swing.event.EventListenerList helpful for this task. For each event listener registration method, it is common practice to define a protected method to generate and fire an appropriate event to all registered listeners. For example, if your component has a public addActionListener() method, you may find it useful to define a protected fireActionEvent() method as well. This method calls the actionPerformed() method of every registered ActionListener object.

3.24.3 Constructors

It is customary to provide a no-argument constructor for a component. This is helpful if you want your component to work with GUI builder tools, for example. In addition, think about how you expect programmers to use your component. If there are a few properties that are likely to be set in most cases, you should define a constructor that takes values for these properties as arguments, to make the component easier to use.

3.24.4 Drawing the Component

Almost every component has some visual appearance. When you define a custom component, you have to write the code that draws the component on the screen. There are several ways you can do this. If you are creating an AWT component, override the paint() method and use the Graphics object that is passed to it to do whatever drawing you need to do.

For Swing components, the paint() method is also responsible for drawing the border and the children of your component, so you should not override it directly. Instead, override the paintComponent() method. This method is passed a Graphics object, just as the paint() method is, and you use this Graphics object to do any drawing you want. As we'll see in Chapter 4, Graphics with AWT and Java 2D, you can cast this Graphics object to a Graphics2D object if you want to use Java 2D features when drawing your component. Keep in mind, however, that a Swing component can be assigned an arbitrary border. Your paintComponent() method should check the size of the border and take this value into account when drawing.

When you define a custom component, you typically have only one look-and-feel in mind, so you can hardcode this look-and-feel as part of the component class itself. If you want your component to support the Swing pluggable look-and-feel architecture, however, you need to separate the drawing and event-handling tasks out into a separate javax.swing.plaf.ComponentUI object. If you do this, you should not override your component's paintComponent() method. Instead, put the painting functionality in the paint() method of the ComponentUI implementation. In order to make this work, you have to override the getUIClassID(), getUI(), setUI(), and updateUI() methods of JComponent.

3.24.5 Handling Events

Most components have some kind of interactive behavior and respond to user-input events such as mouse clicks and drags and key presses. When you are creating a custom component, you must write the code that handles these events. The Swing event-handling model was discussed in Chapter 2. Recall that the high-level way to handle input events is to register appropriate event listeners, such as MouseListener, MouseMotionListener, KeyListener, and FocusListener on your component. If you are using a separate UI delegate object, this object should implement the appropriate listener interfaces, and it should register itself with the appropriate event registration methods on the component when its installUI() method is called.

If you are not using a UI delegate, your component class can handle events at the lower level discussed in Chapter 2. To do this, you override methods such as processMouseEvent(), processMouseMotionEvent(), processKeyEvent(), and processFocusEvent(). In this case, be sure to register your interest in receiving events of the appropriate type by calling enableEvents() in your component's initialization code.

3.24.6 Component Size

Most components have a natural or preferred size that often depends on the settings of various component properties. Many components also have a minimum size below which they cannot adequately display themselves. And some components have a maximum size they wish to enforce. You must write the methods that compute and return these sizes.

If you are using a UI delegate object, you should implement the getMinimumSize(), getPreferredSize(), and getMaximumSize() methods in the delegate. The default JComponent methods call the delegate methods to determine these sizes if the programmer using the component has not overridden the minimum, preferred, or maximum sizes with her own specifications.

If you are not using a UI delegate object, you should override these three methods in the component itself. Ideally, your methods should respect any sizes passed to setMinimumSize(), setPreferredSize() and setMaximumSize(). Unfortunately, the values set by these methods are stored in private fields of JComponent, so you typically have to override both the get and the set methods.

3.24.7 Accessibility

It is a good idea to make your component accessible. In order to do this, your component must implement the javax.accessibility.Accessible interface and its getAccessibleContext() method. This method must return an AccessibleContext object that is customized for your component. You typically implement AccessibleContext as an inner class of the component by extending JComponent.AccessibleJComponent or some subclass of that class. Depending on your component, you may need to implement various other accessibility interfaces on this inner class as well. Studying the accessibility code in existing Swing components can be very helpful in learning how to write your own accessible components. You might start, for example, with the source code for AbstractButton.AccessibleAbstractButton.

3.24.8 Miscellaneous Methods

JComponent defines a number of other methods that you can optionally override to change aspects of a component's behavior. If you take a look at the list of properties defined by the JComponent API, you'll notice that a number of these are read-only properties (i.e., they do not define set methods). The only way to set the value returned by one of these methods is to subclass the method. In general, when you see a read-only property, you should consider it a candidate for subclassing. Here are a few methods of particular interest:

isOpaque()

If the component always fills its entire background, this method should return true. If a component can guarantee that it completely paints itself, Swing can perform some drawing optimizations. JComponent actually does define a setOpaque() method for this property, but your custom component may choose to ignore setOpaque() and override isOpaque().

isOptimizedDrawingEnabled()

If your component has children and allows those children to overlap, it should override this method to return false. Otherwise, leave it as is.

isFocusTraversable()

If your component wants to be included in focus traversal, it should override this method to return true. If your component does not want to be included in the keyboard navigation system, this method should return false.

isFocusCycleRoot()

If your component has children and wants to cycle focus among them, override this method to return true.

isManagingFocus()

If your component needs to receive the Tab and Shift-Tab key events that are normally handled by the focus manager, override this method to return true. If you do, the focus manager uses Ctrl-Tab instead.

Previous Page


iDevJobs.com - Jobs for Professional Developers

Contribute to IDR:

To contribute an article to IDR, a click here.

To contact us at IDevResource.com, use our feedback form, or email us.

To comment on the site contact our webmaster.

Promoted by CyberSavvy UK - website promotion experts

All content © Copyright 2000 IDevResource.com, Disclaimer notice

Visit our NEW WTL Section

Join the Developers Webring

Code Project

Java COM integration

Visit the IDR Forums