Journal Articles

CVu Journal Vol 13, #4 - Aug 2001 + Programming Topics
Browse in : All > Journals > CVu > 134 (6)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: What Is Swing?

Author: Administrator

Date: 03 August 2001 13:15:47 +01:00 or Fri, 03 August 2001 13:15:47 +01:00

Summary: 

Body: 

The Root Pane

Layout managers and child components are never added to a JFrame directly. Instead you must access the content pane of the JFrame and add any layout managers and child components to the ContentPane object.

Why is this so? Well behind the scenes the JRootPane is used internally by all the top-level Swing containers: JWindow, JFrame, JDialog, JApplet, and JInternalFrame. The JRootPane is a special container that manages a fixed ordered layout. It manages a content pane, a layered list of children and potentially a menu bar. The JRootPane prohibits child components from being added directly to itself. Instead you must add any new components to the content pane it manages. This is the literal reason why. The practical reason is that JRootPane is one of the family of different panes, which you can think of as panes in a window, in Swing e.g. JScrollPane, JlayeredPane, JTabbedPane. The root pane is a special pane, because it designed to manage the various panes of a top-level component. Each root pane has a glass pane, a layered pane, a content Pane, and optionally a menu bar.

  1. The glass pane is a JComponent, which sits at the top of the window pane-stack. It is normally transparent, and because it is the highest component, it can potentially trap all input events. It is not normally used except for advanced applications.

  2. The layered pane is a JLayeredPane, which sits one level beneath the glass pane. It manages a list of JInternalFrame objects, which is a component class that support Multiple Document Interface applications in Swing, exactly like Microsoft Office applications.

  3. The content pane is a specific container, say a JPanel, which sits one level beneath the layered pane. The application adds child components to this pane.

  4. The menu bar is an optional slot for a JMenuBar component, which sits at the same level as the content pane. The root pane automatically reserves a space for the JMenuBar if it is used.

Normally you never have to access the root pane container directly, because the top level components, like JFrame for instance, provide convenience methods such as getContentPane(), getMenuBar(), and setMenuBar() .

Actions

Swing introduces a brand new interface class called Action. It basically encapsulates the older java.awt.event.ActionListener into a more useful interface. An action is an association between an action listener, its corresponding behaviour, a list of attributes, and a known public name. You can think of an Action almost like a finite state, because it represents a single behaviour in an application. For instance saving a file, printing a file, or opening an XML document and transforming it into useful content like HTML using a XML stylesheet. The Action class extends the ActionListener and introduces the ability to enable or disable the action, to add and remove a property change listener to listen to events fired by the action.

public interface Action extends java.awt.event.ActionListener
{
  public void addPropertyChangeListener(
      java.beans.PropertyChangeListener listener);
  public void removePropertyChangeListener(
      java.beans.PropertyChangeListener listener);
  public Object getValue(String key);
  public void putValue(String key, Object value);
  public boolean isEnabled();
  public void setEnabled(boolean flag);
  public final String DEFAULT;
  public final String NAME;
  public final String LONG_DESCRIPTION;
  public final String SHORT_DESCRIPTION;
  public final String SMALL_ICON;
}

The interface also defines static string constants to represent standard attribute for all actions. Actions maintain a map of name attributes and values. If, for example, you want to find the icon of the action, you would query the method getValue() with the key SMALL_ICON. The basic idea of the overall interface is to allow any action listener to respond to feedback. This is tremendously useful, because we only need one instance of the behaviour. Say to terminate the application we only need to create one QuitAction object and register it many components. Fortunately Swing provides an abstract base class called AbstractAction, which implements the methods in the interface except for the actionPerformed(). We, then, don't have to create Action object entirely from scratch every time.

public abstract class AbstractAction extends javax.swing.Action
{
  public AbstractAction();
  public AbstractAction(String name);
  public AbstractAction(String name, Icon icon);
  public abstract void actionPerformed(ActionEvent evt);
  ...
}

Enough of theory lets look at the second version of our Swing preferences application. The first task is to copy the file PrefencesApp1.java to a new Java source file called PreferencesApp2.java. The new object class, PreferenceApps2, still subclasses the JFrame class but no longer implements the ActionListener interface directly. Instead we will use inner classes and Actions. Also we remove the old actionPerformed() method. We are also introducing new variables and declaration at the top of the file for AbstractActions and two JLabels widget see later.

public class PreferencesApp2 extends JFrame 
{
  protected JButton  bt_add;
  protected JButton  bt_remove;
  protected JButton  bt_quit;
  protected JList  list_availableFont;
  protected JList  list_configFont;
  protected JLabel  previewLower;
  protected JLabel  previewUpper;
  protected AbstractAction quitAction;
  protected AbstractAction addAction;
  protected AbstractAction removeAction;
  public PreferencesApp2(String title){ ... }
  ...
}

We create our first Action, called QuitAction as an inner nested class. This is very simple action that terminates the application when it is invoked.

public class PreferencesApp2 extends JFrame 
{
  ...
// INNER CLASSES HERE! At the end of the class definition
  protected class QuitAction extends AbstractAction{
    public QuitAction(){
      super("Quit");
      putValue(SHORT_DESCRIPTION, "Stop This Application");
    }
    public void actionPerformed(ActionEvent evt){
      System.out.println("Goodbye.");
      System.exit(0);
    }
  }
}

Since Action class is an ActionListener we can register it directly with the JButtons by calling addActionListener(ActionListener) method.

To finish up this section we need to register our new actions with their respective JButtons.

public class PreferencesApp2 extends JFrame {
  ...
  public PreferencesApp2(String title) {
    super(title);
// First create our actions we need them later
    quitAction = new QuitAction();
    addAction = new AddAction();
    removeAction = new RemoveAction();
    ...
// --------------------------------------------
// User interface is complete up to this point
// --------------------------------------------
    bt_quit.addActionListener(quitAction);
    bt_add.addActionListener(addAction);
    bt_remove.addActionListener(removeAction);
  }
  ...
}

Now we have to define the next two actions AddAction and RemoveAction inner classes. But before we do that, we must make a slight detour.

ListModel

As was explained previously, Swing is built on the MODEL-VIEW-CONTROLLER architecture. The majority of Swing components support an underlying data model interface or abstract class or both. Thus the JList component expects to interact with a ListModel interface class, which has a very simple interface.

public interface ListModel{
  public void addListDataListener(javax.swing.event.ListDataListener listener);
  public void removeListDataListener(javax.swing.event.ListDataListener listener);
  public Object getElementAt(int index);
  public int getSize();
}

The list model like the action interface beforehand provides a way of getting feedback from the model, whenever there is change to the data in the model, events are fired to any registered listeners. In the list model this feedback takes place by registering a ListDataListener object with the model. This interface represents the bare minimum features of a read-only [immutable] list model. By itself the ListModel, then, is not very useful. Swing, however, does provide a more extensive object class called DefaultListModel. The class shares many method signatures, thankfully, with the java.util.Vector class and the much newer java.util.ArrayList Java Collections Framework class. There are far too many methods to list to here but the essential ones that we use in the preferences application are shown below.

public class DefaultListModel  extends javax.swing.AbstractListModel{
  public boolean isEmpty();
  public int getSize();
  public void addElement(Object element);
  public void removeElement(Object element);
  public void removeElementAt(int index);
  public void removeAllElements();
  public Object elementAt(int index);
// alias for getElementAt()
  public Object getElementAt(int index);
  public boolean contains(Object element);
  public int indexOf(Object element);
  public java.util.Enumerator elements();
// Java Collections Interface JDK1.2
  public void add(Object element);
  public Object add(int index);
  public Object add(int index, Object element);
  public Object remove(int index);
  public Object get(int index);
  public void clear();
}

And now we go on to implementing our remaining abstract actions. JList provides a method to get the currently selected item in the list, if any. It is called getSelectedValue() and returns an Object or null if there was no item selected at all. We can use this method to implement the AddAction right now. We effectively transfer one element from one source ListModel to the target.

protected class AddAction extends AbstractAction{
  public AddAction(){
    super("Add");
    putValue(SHORT_DESCRIPTION, "Add Selected Font");
  }
  public void actionPerformed(ActionEvent evt){
    String fontChosen = (String)list_availableFont.getSelectedValue();
    if (fontChosen != null){
      DefaultListModel availModel = (DefaultListModel)list_availableFont.getModel();
      DefaultListModel configModel = (DefaultListModel)list_configFont.getModel();
      if(!configModel.contains(fontChosen)){
        configModel.addElement(fontChosen);
        availModel.removeElement(fontChosen);
      }
    }
  }
}

We, first, check that the available font has a selected item, which is the name of the font family. We get the ListModel type objects from the respective JList objects. (We know that they are really DefaultListModel objects, since we created and add in the constructor, therefore we can safely cast.) We make sure that the font family has not already been added to user configuration list. Finally if has not been, we transfer it. Delete the font family from the available list and add it to the user configuration list. Job done.

The RemoveAction is a mirror image of the AddAction inner class. We just swap the source and target list model objects around in the source code.

protected class RemoveAction extends AbstractAction {
  public RemoveAction(){
    super("Remove");
    putValue(SHORT_DESCRIPTION, "Remove Selected Font");
  }
  public void actionPerformed(ActionEvent evt){
    String fontChosen = (String)list_configFont.getSelectedValue();
    if(fontChosen != null){
      DefaultListModel configModel = (DefaultListModel)list_configFont.getModel();
      DefaultListModel availModel = (DefaultListModel)list_availableFont.getModel();
      if (!availModel.contains(fontChosen)){
        availModel.addElement(fontChosen);
        configModel.removeElement(fontChosen);
      }
    }
  }
}

ListSelectionListener and Event

If you want to find out which items where selected in a JList. You must create a ListSelectionListener object class and interpret the ListSelectionEvent.

The interface for the ListSelectionListener is simply:

public interface ListSelectionListener  extends java.util.EventListener{
  public abstract void valueChanged(ListSelectionEvent evt);
}

The only method that we are contracted to define is valueChanged(). The ListSelectionEvent class is generated by the JList class, or more correctly its internal ListSelectionModel object, whenever the list selection data changes:

public class ListSelectionEvent  extends java.util.EventObject{
// Constructor
  public ListSelectionEvent(Object source, int firstIndex, int lastIndex, boolean adjusting);
// Methods
  public int getFirstIndex();
  public int getLastIndex();
  public boolean getValueIsAdjusting();
}

The getFirstIndex() and getLastIndex() method return the first and last indices of the list model that may have had their selection changed respectively. The ListSelectionEvent does not transmit the items that have been selected or unselected themselves. Therefore in order to find out what the selected items are, we have to inquire about the items directly from the JList or the data model. Incidentally, the source of the event can always be found by calling the getSource() method of the superclass EventObject. The last method getValueIsAdjusting() returns true if the event is one of a series of rapid-fire events. (You may get an event-train if you set a JList to allow multiple selections. See the Java documentation on the JList method setSelectionMode() for more information.) In other words the list selection event can be fired multiple times in succession, so be careful to check that this method returns false before you do something permanent.

Finally to register a list selection listener object we call the JList method :addListSelectionListener(ListSelectionListerner).

We, now, can make use of the information to build some extra functionality into the preferences application. We would like to allow the user to see the preview of the font before he or she added it to first user configuration. We can display a preview of the font family at a fixed size and weight. Whenever the selection in either of the list components changes we arrange to change the font of the preview labels. We can make use of those two JLabel declarations that we set aside previously. We write a simple text string into both labels and have one display the content in upper case and the other one in lower case. To make this happen we add some more lines to the PreferencesApp2 constructor where the user interface is created:

public class PreferencesApp2 extends JFrame {
  ...
  public PreferencesApp2(String title){
    super(title);
    ...
// Add another vertical box to preview the currently selected list font as two labels widgets
    Box box2 = Box.createVerticalBox();
    container.add(box2, BorderLayout.SOUTH);
    previewLower = new JLabel("the quick brown fox jumps over the lazy dog");
    previewLower.setFont(new Font("Arial", Font.PLAIN, 18));
    box2.add(Box.createVerticalStrut(20));
    box2.add(previewLower);
    previewUpper = new JLabel("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG");
      previewUpper.setFont(new Font("Arial", Font.PLAIN, 18));
    box2.add(Box.createVerticalStrut(2));
    box2.add(previewUpper);
// Add a preview font list selection listener to each list
    PreviewFontHandler previewHandler = new PreviewFontHandler();
    list_availableFont.addListSelectionListener(previewHandler);
    list_configFont.addListSelectionListener(previewHandler);
// User interface is complete up to this point
    ...
  }
  ...
}

The only part left now is to define the PreviewFontHandler the custom ListSelectionListener inner class. The valueChanged() method, first, makes sure that the event is the last of the series of rapid-fire events. Having retrieved the chosen font family from the affected JList component, the method proceeds to change the font of the preview label based on the chosen font family.

protected class PreviewFontHandler implements ListSelectionListener{
  public void valueChanged(ListSelectionEvent evt) {
    if(evt.getValueIsAdjusting()) return; // Ignore rapid-fire events
// Check the event source is really a JList
    Object source = evt.getSource();
    if(source == list_availableFont || source == list_configFont){
      JList listGUI = (JList)source;
      String fontChosen = (String)listGUI.getSelectedValue();
      Font font = new Font(fontChosen, Font.PLAIN, 18);
      previewUpper.setFont(font);
      previewLower.setFont(font);
    }
  }
}

MenuBars , MenuItems, and Menus

The top-level components in Swing can accept an optionally JMenuBar component. To set a menu bar for a JFrame component you call the setJMenuBar(JmenuBar) method. Creating a menu bar component is very easy. You just call it with a default constructor. A menu bar allows a pull down menu to be added with the method add(JMenu) and removed with the method remove(JMenu). The menus are laid out by a special layout manager, which arranges the menus in order of their addition from left to right. A special slot, on the far right hand side, is reserved for a help pull down menu. You can designate which menu is the help menu with the method setHelpMenu(JMenu). The JMenuBar subclasses from JComponent class.

JMenuBar also implements the MenuElement interface which contracts all the methods that menu components must define in order to handle navigation and event handling in a standard way.

public JMenuBar extends JComponent implements MenuElement{
// Constructors
  public JMenuBar();
// methods
  public add(JMenu menu);
  public remove(JMenu menu);
  public setHelpMenu(JMenu menu);
  public JmenuItem getHelpMenu();
  ...
}

The individual menu component is JMenuItem and subclasses directly from AbstractButton class, so it shares much of the functionality of the JButton class. You can register an action listener with a menu for instance. The menu item component makes more use of the super class's functionality, the ability to set keyboard accelerator, or declare a mnemonic key.

The pull down component is JMenu and subclasses from JMenuItem class, which might strike you as being strange object class design, until you realise that there are such things as cascading menus - pull down menus with menu items that invoke another pull down menu. JMenuItems can be added to a JMenu with a few convenience methods.

public JMenu extends JmenuItem implements MenuElement{
// Constructors
  public JMenu();
  public JMenu(String name);
  ...
// Methods
  public JMenuItem add(String s);
  public JMenuItem add(JMenuItem item);
  public JMenuItem add(Action action);
  ...
}

The JMenuItem method add(Action) is extremely useful. It allows us to conveniently create a JMenuItem and simultaneously register a binding Action. If for example you disabled the action by calling setEnabled(false) then that change would be propagated back to the JMenuItem. It could then render itself as a greyed out button, and/or lock out any mouse event behaviour. Additionally other Swing components, JMenu, JPopupMenu, and JToolbar directly allow Actions to be added to them. They make use of the standard attributes of the Action interface like SHORT_DESCRIPTION and SMALL_ICON to automate the creation of tool tips, and image icons. (Unfortunately for JButton, JToggleButton, and JCheckBox do not provide this easy binding of actions to components. Something that I think is missing from the AbstractButton API framework!)

To add a menu bar and pulldown menus to our preferences application we again have to modify the constructor.

public class PreferencesApp2 extends JFrame {
  ...
  public PreferencesApp2(String title){
    ...
// Create a second empty model for user configuration
    DefaultListModel configModel = new DefaultListModel();
// Create the menu bar and pulldown menu
    JMenuBar menubar = new JMenuBar();
    setJMenuBar(menubar);
    JMenu fileMenu = new JMenu("File");
    menubar.add(fileMenu);
    JMenuItem addMI = fileMenu.add(addAction);
    addMI.setMnemonic('A');
    JMenuItem removeMI = fileMenu.add(removeAction);
    removeMI.setMnemonic('R');
    fileMenu.addSeparator();
    JMenuItem quitMI = fileMenu.add(quitAction);
    quitMI.setMnemonic('Q');
    quitMI.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_MASK));
    ...
  }
}

Conclusion

This completes the second version of the application. You have learn about inner workings of the top level component, in particular the organisation of the root pane container. You have learnt about Actions. You have learnt about the list data model and how data models interact with the Swing components. Finally you should understand how Swing menus, menu items, and menu bars are put together for an application.

Download the entire article and finished example application from http://www.xenonsoft.demon.co.uk/accu.html. Enjoy.

Books and References

Java 2D Graphics by Jonathan Knudsen (1 56592 484 3), O'Reilly

Java Swing by Robert Eckstein, Marc Loy & Dave Wood (1 56592 455 X), O'Reilly

Swing by Matthew Robinson and Pavel Vorobiev (1 88477784 8), Manning

http://java.sun.com/products/jfc/ Sun's product home page of the JFC

http://java.sun.com/products/jfc/ whitepaper.html A white paper of the JFC philosophy

http://java.sun.com/products/jfc/tsc/ The Swing Connection Page

http://www.algonet.se/~set_lo/java/sbe/ On line book Swing mirror in Sweden

http://www2.gol.com/users/tame/swing/ examples/SwingExamples.html Tamemasu Nobuo's brilliant Swing JFC examples with great screen shots (intermediate/advanced)

http://www.jguru.com/jguru/faq/faqpage .jsp?name=Swing jGuru excellent frequently asked questions on Swing

Notes: 

More fields may be available via dynamicdata ..