Descriptor Logo

JFC Tabbed Dialogs with an Apply Button

 

I guess we all know by now that Java is supposed to be cross platform. The new Java 2 platform (formerly known as JDK 1.2) has even more evidence of that - it includes the Java Foundation Classes (JFC), which let you create truly cross-platform user interfaces.

But even so, it's also a fact of life that the vast majority of PC users run some form of Microsoft Windows -- and they expect their user interfaces to adhere to Windows conventions. One prominent user interface feature in Windows is the property sheet, which gathers together several dialogs into a nice, tidy package.

JFC supports a similar notion with the JTabbedPane class, which lets you build tabbed dialogs that look and act like property sheets. What JFC is missing, though, is any built-in support for the "Apply" button that most Windows property sheets contain. This article presents code that makes it easy to create JFC tabbed dialogs with functional Apply buttons. See Figure 1 for an illustration of such a property sheet using the Microsoft Windows look and feel.

Screen Image

Figure 1: A JFC Property Sheet

I modeled the user interface for my property sheet code using the Microsoft Windows User Interface Guidelines. When the user first displays the property sheet, the Apply button is disabled. Once the user changes something in a page, for example, selects a new coach in the list, the property sheet enables the Apply button. If the user presses Apply, the property sheet notifies the main program so that the main program can act on the event, but the property sheet does not dismiss. The act of pressing the Apply button also disables it so that the user can only press Apply if they have changed something in one of the pages. The OK and Cancel buttons work as usual - both dismiss the property sheet, but the main program only takes action if the user OKed the property sheet.

The Apply button is the interesting part - it must somehow notify the main program that at least one page has changed without dismissing the property sheet. To accomplish this, I choose to use Java's standard event handling model - each page will act as an event source and the main program will be a listener.

The JTabbedPane component that makes up the heart of a property sheet has a rather complex structure. A tabbed pane consists of pages, sometimes referred to as tabs. Each page is a JFC component, usually deriving from JPanel, and has a title, which is displayed in the tab. In Figure 1, the tabbed pane contains two pages - one that shows a list of prominent college football coaches, and one that lets the user choose the horizontal alignment of text strings.

The buttons on the dialog are not provided by the JTabbedPane component. So typically, you write a subclass of JDialog that contains the tabbed pane and the buttons as child components. In other words, instead of repeating the buttons on each page, we place the buttons on the dialog that contains the pages.

Figure 2 shows my property sheet classes, presented in Unified Modeling Language (UML) notation.

UML Diagram Image

Figure 2: The Property Sheet class diagram

JDialog, JPanel, EventListener and EventObject are provided by JFC while I wrote the rest. The PropertySheet class subclasses JDialog and provides the tabbed-pane control and the buttons. As you can see from Figure 2, it defines two methods: the addPage method lets you append a page to the property sheet and the getRet method lets you determine whether the user pressed the OK or Cancel buttons to dismiss the property sheet.

The PropertyPage class is the superclass for all pages including the Coaches and Alignment pages shown in Figure 1. PropertyPage defines three interesting methods that follow the standard Java convention to let components notify other components of events. In this case, the event is that the user modified something on the page and then pressed the Apply button. The PropertyPage class handles all of the notification so that subclasses, like AlignmentPanel and CoachesPanel don't have to worry about it.

The PropertyPageListener interface defines the behavior required for components that want to be notified when the Apply event occurs. The PropertyPageEvent class defines the object that's passed as an argument in the pageChanged method in the listener interface. You will see how this works in a moment.

To use the property sheet classes, first you need to write a subclass of PropertyPage for each page that you want in your property sheet. Each page will probably have user-input components, such as lists, radio buttons, text-entry fields and the like. For each user-input component, the PropertyPage subclass should define "set" and "get" methods so that the main program can initialize and retrieve data from the page. Here's a snippet of code from the "Coaches" page shows the "set" and "get" methods to access the JList component referenced by the currentCoach field (see the end of this article for complete code listings):

public String getCurrentCoach() 
{ 
    currentCoach = (String)coachesList.getSelectedValue();          
    return currentCoach; 
} 
public void setCurrentCoach ( String newValue )          
{ 
    this.currentCoach = newValue; 
    coachesList.setSelectedValue ( currentCoach, true ); 
} 
The property page also needs to tell the property page when to enable the Apply button (remember that it's initially disabled). The following code, again from the Coaches page, shows how to do this:
coachesList.addListSelectionListener (
	new ListSelectionListener()
	{
	    public void valueChanged ( ListSelectionEvent evt )
	    {
		if ( evt.getValueIsAdjusting() == false )
		{
		    setModified ( true );
		}
	    }
	}
  );
}


This code uses a Java language feature known as an anonymous inner class to register a listener on the JList component that displays the coaches list. Whenever the user selects a new list entry, the JList generates the ListSelectionEvent, which we respond to by calling the setModified method. (We first call getValueAdjusting to make sure that the event is for a selection, not a deselection. If we don't, then our handling code will run twice each time the user changes the list selection.) The setModified method is defined by the page's superclass, PropertyPage - it notifies the property sheet to enable the Apply button.

You also need to write a main program that:

  1. Creates the property sheet
  2. Creates each page object and calls its "set" methods to initialize the page's user-input components
  3. Registers as a listener on each page so that if the user presses Apply, the main program can update its data and display
  4. Adds each page to the property sheet
  5. Displays the property sheet modally
  6. If the user pressed OK to dismiss the property sheet, update the main window's data and display

The first step is to create the property sheet using code like:

PropertySheet ps = new PropertySheet ( jf, "Options" );

Note that the PropertySheet constructor expects you to pass the parent frame and the string that's displayed in the property sheet's titlebar. Then you create and initialize each page:

AlignmentPanel ap = new AlignmentPanel ( ps ); 
CoachesPanel = new CoachesPanel( ps );
The PropertyPage constructor requires a reference to the PropertySheet that contains the page, so that the page can tell the property sheet to enable the Apply button. Next we need to call the "set" methods on the page object so it can initialize its user-input components. The sample main program uses a JLabel component (coachText) to display the coach string; here we show using its current contents and alignment to initialize the two pages (see above for the code for the "set" method for the Coaches page):
ap.setAlign ( coachText.getHorizontalAlignment() );
cp.setCurrentCoach ( coachText.getText() );
The next step is to register for the event that signals that the user pressed the Apply button. Here we use anonymous inner classes to register and respond:
ap.addPageListener ( new PropertyPageListener()
    {
        public void pageChanged ( PropertyPageEvent ppe )
        {
            coachText.setHorizontalAlignment ( ap.getAlign() );
        }
    }
  );

cp.addPageListener ( new PropertyPageListener()
    {
        public void pageChanged ( PropertyPageEvent ppe )
        {
            coachText.setText ( cp.getCurrentCoach() );
        }
    }
  );
Each event handler class implements the PropertyPageListener interface by providing the pageChanged method. In these methods, we call the "get" methods on the page and use the results to update the main program's JLabel. Thus the main program will update its display whenever the user presses the Apply button.

The next step is to add the pages to the property sheet and to display the sheet. Here's that code:

ps.addPage ( "Coaches", cp );
ps.addPage ( "Alignment", ap );
ps.show();
To add a page, we specify the page's tab text and the page reference. The show method displays the property sheet modally - it will not return until the user dismisses the property sheet by pressing either OK or Cancel. When the user does, we can write code to handle the OK action:
int ret = ps.getRet();


if ( ret == JOptionPane.OK_OPTION )
{
    if ( cp.getModified() == true )
    {
        coachText.setText ( cp.getCurrentCoach() );
    }

    if ( ap.getModified() == true )
    {
        coachText.setHorizontalAlignment ( ap.getAlign() );
    }
}
This code first retrieves the button-press code from the property sheet. If the user pressed OK, we retrieve the data from a page and update the main program's display. Note that, as an optimization, we only update if the page is "dirty" - we can tell by querying the page's modified flag. The PropertyPage class sets this flag to true when the page calls setModified, and resets it when the user presses the Apply button.

Note that you don't have to worry about writing any code for the Apply button itself - that's all handled by the PropertyPage and PropertySheet classes. That's the beauty of object-oriented programming - it lets you write classes that encapsulate the "dirty work" so you can concentrate on your own program's code.

Obtaining the Program's Code

You can download the entire source for the property sheet program, including the sample Alignment and Coaches pages and a testing program, by clicking the link below. The files are packaged as a ZIP file -- be sure to use an unzipper that can handle long filenames and will recreate the directory structure. Once you have unzipped the files, examine the PropertySheet.txt file for instructions on how to run the sample. Click here to browse the API documentation for the PropertySheet classes.

Download PropertySheet.zip

Contact Information

To learn more about how Descriptor Systems can supply training and programming services for your company, choose one of the following:
  • Email: Joel Barnum
  • Phone: 888-849-5350 toll-free in the USA
  • Phone: 319-362-3906
  • Cellphone: 319-270-1227

Our mailing address is:

Descriptor Systems
P.O. Box 461
Marion IA 52302 USA

Copyright © Descriptor Systems, 2000, 2001. All rights reserved.

All trademarks are owned by their respective companies.