Solve the date-selection problem once and for all

Implement a date-selection widget using the Decorator pattern

Date selection is a problem that pops up every few months when you build client-side graphical user interfaces (GUIs). Unfortunately, Java doesn't provide anything like a DateChooser class, and the date-selection widgets I found in a recent Web search were too heavyweight for my purposes. I wanted something clean, unobtrusive, and flexible. Figure 1 shows a few variants of this article's class, so you can see what I mean by "clean" at the display level.

Figure 1. Various ways to present a Date_selector object

The free off-the-Web solutions were great examples of the you-get-what-you-pay-for principle: amateurish code that must be completely rewritten to be useful. This article's example, in fact, started out as an off-the-Web control, but by the time I finished fixing it, not a single line of the original code remained, and the architecture had completely changed (or, I should say, I added an architecture to an amorphous Java mass). So much for "free."

My widget also provides a good Decorator design pattern demonstration and a good look at Java's Calendar class, and it shows how to implement your own title bars and frames on a Swing JDialog.

Decorators

As is the case with any complex problem, the solution is greatly simplified by breaking the big problem up into smaller problems, each of which can be easily implemented (and tested). The Decorator pattern is great for this approach.

You've seen decorators if you've used Java's input/output (I/O) classes. The complex problem is efficiently reading a compressed stream of bytes, for example. You can do this with one massive class, but it's easier to break the problem up into three distinct subproblems:

  1. Read bytes
  2. Make reads more efficient with buffering
  3. Decompress a stream of bytes

Java solves the first problem with a FileInputStream, instantiated like this:

    try
    {   InputStream in = new FileInputStream( "file.name" );
    }
    catch( IOException e )
    {   System.err.println( "Couldn't open file.name" );
        e.printStackTrace();
    }

You then add buffering with a decoration (or "wrapping") strategy. You wrap the InputStream object with another InputStream implementor that buffers bytes. You ask the wrapper for a byte; it asks the wrapped stream for many bytes and returns the first one. The decorator wrapping goes like this:

    try
    {   InputStream in = new FileInputStream( "file.name" );
        in = new BufferedInputStream( in );
    }
    catch( IOException e )
    {   System.err.println( "Couldn't open file.name" );
        e.printStackTrace();
    }

Add decompression with another decorator:

    try
    {   InputStream in = new FileInputStream( "file.name" );
        in = new BufferedInputStream( in );
        in = new GZipInputStream( in );
    }
    catch( IOException e )
    {   System.err.println( "Couldn't open file.name" );
        e.printStackTrace();
    }

You can increase filtering by adding more decorators.

This solution is very flexible. You can mix and max the decorators to get the feature mix you need. More importantly, each decorator is relatively simple because it solves only one problem. Consequently, the decorators are easy to write, debug, and modify without affecting the rest of the system. I can change the buffering algorithm by rewriting BufferedInputStream, for example, and not touch any other decorators (or any code that uses them). I can also add new filter functionality simply by implementing a new decorator. (Classes like CipherInputStream were added to Java this way.)

Date selection from the center out

I apply the Decorator principle to the date-selection problem. The core problem is displaying a calendar for a single month, so I start by implementing a class that does only that. Figure 2 shows the Date_selector_panel user interface. It's just a calendar, but it's easy to create because I don't have to clutter the code with unnecessary details. "Today" is highlighted. Select a date by clicking on it. (ActionListener objects registered with the widget are notified when you select a date.) The background is transparent by default—it appears gray here because the underlying window is gray.

Figure 2. A raw Date_selector_panel, created with the no-arg constructor

This raw date selector can be "decorated" in several ways to make it more useful. First, you can add a navigation bar to the bottom (shown in Figure 3) to move the calendar one month (single arrow) or one year (double arrow) forward (right-pointing arrow) or backward (left-pointing arrow).

Figure 3. Add navigability

You can add navigation bars with a decorator object that wraps the raw Date_selector_panel. Both the wrapper and the underlying panel implement the Date_selector interface, so you can use them interchangeably. The following code creates the decorated date selector in Figure 3:

Date_selector selector = new Date_selector_panel();
selector = new Navigable_date_selector( selector );

The same effect is accomplished with a convenience constructor that creates the wrapped Date_selector_panel for you:

Date_selector selector = new Navigable_date_selector();

The other augmentation I've supported is a title that shows the month name and year that displays on the calendar (see Figure 4).

Figure 4. Add a title

I use the same decoration strategy as before to add the title:

Date_selector selector = new Date_selector_panel();
selector = new Navigable_date_selector( selector );
selector = new Titled_date_selector   ( selector );

Leave out the navigation bar by omitting the second line in the above code. It doesn't matter which order you mix in the title and navigation bars if you need both. Again, a convenience constructor creates a titled date selector (without the navigation bar) as follows:

Date_selector selector = new Titled_date_selector();

The final variant is Figure 5's lightweight pop-up dialog. It can be dragged by the title bar (though dragging can be disabled) and closed by clicking on the close icon in the upper right corner.

Figure 5. A date-selection dialog

As before, use a decorator to manufacture a dialog:

Date_selector selector = new Date_selector_panel();
selector = new Navigable_date_selector( selector ); // Add navigation
Date_selector_dialog popup = new Date_selector_dialog( selector );
//...
Date = popup.select();  // Returns the user-selected date, or null if the
                        // dialog was closed without selecting anything.
// Or you can do this:
popup.setVisible( true );           // Pop it up. Block until dismissal.
Date = popup.get_selected_date();   // Get the selected date.

Note that you don't need to mix in a title here because you use the dialog-box title bar for that purpose. As before, the Date_selector_dialog defines a convenience constructor to create a navigable dialog box like the one in Figure 5:

Date_selector = new Date_selector_dialog();

All the earlier examples create a calendar for the current month. Several methods can programmatically change the date. For the most part, they work like the java.util.Calendar class's similar methods.

Implement Date_selector

I implement the core Date_selector_panel as an interface to make the Decorator pattern viable. (In the I/O example, all the decorators are InputStream objects, so they can be treated interchangeably. The constructor arguments to all the decorators are all InputStream references. You can wrap any decorator with another decorator without the latter knowing what it's wrapping.)

The core interface looks like this (I left out the Javadoc and other extraneous code in the interest of clarity. See Resources for the complete code):

public interface Date_selector
{
    public static final int CHANGE_ACTION = 0;
    public static final int SELECT_ACTION = 1;
    public void addActionListener(ActionListener l);
    public void removeActionListener(ActionListener l);
    public Date get_selected_date();
    public Date get_current_date();
    /** Must work just like {@link Calendar#roll(int,boolean)} */
    public void roll(int flag, boolean up);
    /** Must work just like {@link Calendar#get(int)} */
    public int get(int flag);
}

The interface defines two constant values, CHANGE_ACTION and SELECT_ACTION, used in event processing (more in a moment). The interface provides ways to add and remove ActionListener objects that are notified when a date is selected. Finally, it provides an interface that mimics the java.util.Calendar() methods to advance the calendar by some increment (a month, typically) and get an attribute's value (such as the month or year).

Let's return to the action listeners: add listeners to your date selector in the same way you add a listener to a button:

Date_selector selector = new Date_selector_panel();
selector.addActionListener
(   new ActionListener()
    {   public void actionPerformed( ActionEvent e )
        {   // Do whatever you'd do to process an event
        }
    }
);

The listener strategy is a realization of the Observer design pattern.

Listeners are notified (actionPerformed(...) is called) in two situations, which can be distinguished from one another by sending the ActionEvent object passed to actionPerformed a getID() message. The table below lists the details.

Action events

getID()returnsDescription
CHANGE_ACTIONThis event is sent when the calendar panel changes the displayed month or year (typically because some sort of navigator bar asked it to). Call event.getActionCommand getActionCommand() to get a string holding the current (after the scroll) month and year. You can also call get_current_date() to get the date the user selected.
SELECT_ACTIONSent every time the user clicks on a date. Call event.getActionCommand getActionCommand() to get a string representing the selected date. (This string takes the same form as the one returned by toString.) You can also call get_selected_date() to get the date the user selected.

The following code demonstrates the two event types. It sets up any Date_selector to update a label every time the user navigates to a new month and also prints the selected date to the console when the user clicks on it:

Date_selector selector = new Date_selector_panel();
JLabel moth_display = new JLabel();
s.addActionListener
(   new ActionListener()
    {   public void actionPerformed( ActionEvent e )
        {   if( e.getID() == Date_selector.CHANGE_ACTION )
                month_display.setText( e.getActionCommand() );
            else
                System.out.println( e.getActionCommand() );
        }
    }
);

All classes that implement Date_selector also extend JPanel (or at least some Container derivative). I want to mandate this requirement in the code so that a Date_selector can also be used as a JPanel without a cast, but I can't do that here because java.awt.Container is not an interface. For a method to return something that can be treated as a Date_selector or Container, it would have to implement both classes, which is illegal. Also, an interface (Date_selector) can't extend a class (Container).

This deficiency is a good example of how it's sometimes difficult to retrofit Gang of Four patterns on existing code that doesn't appropriately use interfaces. If this code was my own, I would refactor it to make Container an interface and then implement both Container and Date_selector in my decorators. (I obviously can't change a class in a java.* package, however). This class-to-interface refactoring is difficult because all the code that instantiated the class-now-interface using new would break.

One of the few reasons to introduce C++-style operator overloading into Java is to make this sort of refactoring easy by overloading operator new. That way, you can new an interface and have the overload provide an instance of a default implementation class. The Java solution is to introduce a Gang of Four factory, but it's a lot easier to do things right from the beginning.

Implement the basic selector

The core date-selection class is Date_selector_panel. Looking at it in pieces, I start with a few variable declarations, shown below. Don't be thrown by the "instance initializer" syntax. (Think of the code in parentheses that doesn't seem to be attached to anything as a static initializer block without the static.) Instance initializers are concatenated in declaration order and then append to the top of every constructor that doesn't chain to another constructor using the this(...) construct. They provide a way to initialize a field with a guarantee that the initialization code will effectively appear in every constructor. It's also convenient to put all initialization code for a given field in one place at the point of initialization. That way, no uninitialized (or partially initialized) variables lurk around causing runtime errors. Moving the code to the constructor makes maintenance harder because you must check two places when you make a change.

The code in parentheses following the calendar declaration, for example, is an instance initializer:

1 2 3 4 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more