Chapter 3. Advanced Topics

Table of Contents

1. How to Create a Completely New Look for the Components
1.1. Concepts: About UI delegates
1.2. Per-Application vs Per-Component Customization
1.3. Creating a Custom UI Delegate
1.4. Example 1: Read-Only Calendar Title
1.5. Example 2: Spinner Calendar (Windows Date/Time Calendar)
1.6. Example 3: Image Scroll Buttons for the Calendar
2. ADF JClient Binding
3. JGoodies Binding
4. JBuilder's dbSwing Binding

1. How to Create a Completely New Look for the Components

The JDatePicker suite components were designed using the same philosophy as the standard Swing components. This means, among other things, that their actual look and feel is separated (isolated) in a UI delegate object. It is this delegate that builds the graphical interface of the component that is seen and used by the final users.

This section discusses how you can take advantage of the delegate mechanism to further customize the components to fit even the wildest requirements. It shows what the UI delegates are and how you can register them on the corresponding components. A few examples of custom UI delegates are also presented.

Before reading further, keep in mind that you should consider using a custom UI delegate only if you cannot configure the component to do what you need. This section is here for reference purposes. Usually, if you need a custom delegate, we will help you implement it.

1.1. Concepts: About UI delegates

You might want to skip this section if you are familiar to the Swing architecture. It discusses what UI delegates are and why they are important for Swing. A detailed article on this subject is A Swing Architecture Overview.

When someone refers to a Swing component, it usually means an instance of a class derived from JComponent. But a Swing component is more than that. It is composed of:

  • a model

  • a component (extends JComponent)

  • a UI delegate (extends ComponentUI)

The model maintains data specific for the Swing component. Whenever this data changes, the registered listeners are notified about it. The component contains the actual API, many of its methods being wrapper methods for the model. The UI delegate controls how the component is painted on the screen. It is that part of the component that the user sees and interacts with.

Both the model and the UI delegate are defined by interfaces which means that there can be various implementations for them. In the case of the UI delegate, we could have more than one way of presenting the component to the user.

In fact, it is this mechanism that allows Swing to support multiple look and feels. For instance, the JButton component can have multiple delegates like MetalButtonUI, WindowsButtonUI and MotifButtonUI. Each of these delegates is registered on JButton when a certain look and feel is active.

UI delegates can be used to provide a completely different face for a component. If you find that the UI delegate of a component is not appropriate for your application, you might decide to create a new one. Depending on the complexity of the component, this might prove to be a very complicated thing to do.

1.2. Per-Application vs Per-Component Customization

The JDatePicker suite components can be customized to use a UI delegate in two different ways. You can have a default delegate for all the instances of a component (per-application) or a particular delegate for only some instances of a component (per-component).

All components are configured to use a default UI delegate. For instance, the default UI delegate for JMonth is DefaultMonthUI. This means that whenever you are using a JMonth component, it will use a DefaultMonthUI delegate to present the component. You can change this setting and use whatever UI delegate as the default one for JMonth. This is per-application customization.

To change the default UI delegate for a JDatePicker suite component, you have to register it with the UIManager. Let's assume that you have a UI delegate for JMonth called SimpleTitleMonthUI and you want to make it the default delegate. The following line of code shows how you can do that:

UIManager.put("MonthUI", SimpleTitleMonthUI.class.getName());

In this example, MonthUI is the identifier for the default JMonth UI delegate. The identifiers for the other components are: DatePickerUI for JDatePicker, DateFieldUI for JDateField and MonthViewUI for JMonthView.

But some of the JMonth components that you may use in an application might require a slightly modified user interface. Let's say that only in one input dialog you need to use a JMonth with a modified title. To accomplish this, you need to make that JMonth use a different UI delegate than the default one. This is per-component customization.

To have a particular delegate for only some instances of a component you need to explicitly specify it using the setUI method. Let's say that you don't want SimpleTitleMonthUI to replace the default UI delegate. You want to use it only for a certain instance of a JMonth. The following lines of code show how you can do that:

JMonth jMonth = new JMonth();
jMonth.setUI((DateUI)SimpleTitleMonthUI.createUI(jMonth));

But if the Look and Feel is changed, the specified UI delegate is lost and the default one is used. To avoid this problem you should override the updateUI method like this:

JMonth jMonth = new JMonth() {
  public void updateUI() {
    setUI((DateUI)SimpleTitleMonthUI.createUI(this));
    invalidate();
  }
};

1.3. Creating a Custom UI Delegate

The whole purpose of this discussion on UI delegates is to show how you can further customize the look of the components. You can create UI delegates for the JDatePicker suite components or any other Swing components from scratch or starting from some basic implementation.

Swing contains basic UI delegates for all of its components. For JButton there is a BasicButtonUI delegate. When you decide to create a new ButtonUI delegate you should consider extending BasicButtonUI instead of creating one from scratch.

The same is true for the JDatePicker suite components. Instead of creating UI delegates from scratch, the API provides some basic implementations that can be extended to save time. For instance, there is a BasicMonthUI delegate that can be extended to create delegates for JMonth. The UI delegate API is explained in the com.standbysoft.component.date.swing.plaf.basic package.

Creating a UI delegate usually means extending one of the basic implementations (like BasicMonthUI) and then overriding some base methods to plug your own behavior. The following examples are a good starting point for getting hands on experience.

1.4. Example 1: Read-Only Calendar Title

This section shows how to create a simple UI delegate for JMonth. It has a title that only displays the name of the current month and year. The source code for this delegate can be consulted here.

Step 1: Create the class

public class MonthROTitleUI extends BasicMonthUI {
  public static ComponentUI createUI(JComponent c) {
    return new MonthROTitleUI();
  }
}

To start, you have to create a class that extends BasicMonthUI and overrides the createUI factory method. This method is used internally by Swing when this UI delegate is registered on a JMonth component.

Step 2: Create the title label

public class MonthROTitleUI extends BasicMonthUI {
  protected JLabel monthYearTitle;
  ...
  protected JComponent createTitleMonth() {
    monthYearTitle = new JLabel("", SwingConstants.CENTER);
    return monthYearTitle;
  }
}

The delegate uses a label to display the current month name and year in the title. The class BasicMonthUI uses the createTitleMonth method to create the component that represents the month in the title area. If the method returns null then it means that there is no such component. But in our case, it will return the label that shows the month.

Step 3: Keep the title updated

public class MonthROTitleUI extends BasicMonthUI {
  ...
  protected void updateTitle() {
    int m = month.getMonth();
    int y = month.getYear();
    monthYearTitle.setText(getMonthNames()[m] + " " + y);
  }
}

Having created the month representing label is not enough because it must show accurate information. To do that, you must override the updateTitle method that is called whenever this information changes (locale, changed month, changed year) and change the text displayed on the label.

And you're done.

1.5. Example 2: Spinner Calendar (Windows Date/Time Calendar)

This example shows a more sophisticated UI delegate than the one presented above. It also displays in the title area the month and the year but not with just a simple label. It uses a JComboBox to represent the months and a JSpinner to represent the years. Using this delegate, will make JMonth resemble more to the Windows Date/Time Properties calendar. The source code for this delegate can be consulted here.

Step 1: Create the class

public class MonthWindowsDateTimeUI extends BasicMonthUI {
  public static ComponentUI createUI(JComponent c) {
    return new MonthWindowsDateTimeUI();
  }
}

Step 2: Create the title month combo box

public class MonthWindowsDateTimeUI extends BasicMonthUI {
  protected JComboBox monthTitleComboBox;
  ...
  protected JComponent createTitleMonth() {
    monthTitleComboBox = new JComboBox(new Integer[]{
      new Integer(Calendar.JANUARY), new Integer(Calendar.FEBRUARY), new Integer(Calendar.MARCH), 
      new Integer(Calendar.APRIL), new Integer(Calendar.MAY), new Integer(Calendar.JUNE), 
      new Integer(Calendar.JULY), new Integer(Calendar.AUGUST), new Integer(Calendar.SEPTEMBER), 
      new Integer(Calendar.OCTOBER), new Integer(Calendar.NOVEMBER), new Integer(Calendar.DECEMBER)
      });

    monthTitleComboBox.setMaximumRowCount(12);

    //make sure the combo displays the name of the month correctly
    monthTitleComboBox.setRenderer(new DefaultListCellRenderer() {
      public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

        int month = ((Integer)value).intValue();
        label.setText(getMonthNames()[month]);
     
        return label;
      }
    });

    //the combo controls the displayed month
    monthTitleComboBox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        month.setMonth(((Integer)monthTitleComboBox.getSelectedItem()).intValue());
      }
    });

    return monthTitleComboBox;
  }
}

Step 3: Create the title year spinner

public class MonthWindowsDateTimeUI extends BasicMonthUI {
  protected JSpinner yearTitleSpinner;
  ...
  protected JComponent createTitleYear() {
    yearTitleSpinner = new JSpinner(new SpinnerNumberModel(month.getYear(), 0, 9999, 1));
    yearTitleSpinner.setEditor(new JSpinner.NumberEditor(yearTitleSpinner, "#"));

    //the spinner controls the displayed year
    yearTitleSpinner.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        Integer value = (Integer)yearTitleSpinner.getValue();
        month.setYear(value.intValue());
      }
    });

    return yearTitleSpinner;
  }
}

Step 4: Keep the title updated

public class MonthWindowsDateTimeUI extends BasicMonthUI {
  ...
  protected void updateTitle() {
    int m = month.getMonth();
    int y = month.getYear();

    monthTitleComboBox.setSelectedItem(new Integer(m));
    yearTitleSpinner.setValue(new Integer(y));
  }
}

1.6. Example 3: Image Scroll Buttons for the Calendar

This example shows how to create a MonthViewUI delegate that creates custom image scrolling buttons. The source code for this delegate can be consulted here.

Step 1: Create the class

public class MonthViewYearButtonsUI extends DefaultMonthViewUI {
  public static ComponentUI createUI(JComponent c) {
    return new MonthViewYearButtonsUI();
  }
}

We extend DefaultMonthViewUI because it provides implementations for the today and none buttons. BasicMonthViewUI provides a basic implementation without such buttons.

Step 2: Create the buttons

public class MonthViewYearButtonsUI extends DefaultMonthViewUI {
  ...
  protected JButton createPreviousMonthButton() {
    JButton button = new ScrollButton(new ImageIcon(getClass().getResource("/toolbarButtonGraphics/media/StepBack16.gif")));
    return button; 
  }

  protected JButton createNextMonthButton() {
    JButton button = new ScrollButton(new ImageIcon(getClass().getResource("/toolbarButtonGraphics/media/StepForward16.gif")));
    return button;
  }
    
  protected JButton createPreviousYearButton() {
    JButton button = new ScrollButton(new ImageIcon(getClass().getResource("/toolbarButtonGraphics/media/Rewind16.gif")));
    return button;
  }

  protected JButton createNextYearButton() {
    JButton button = new ScrollButton(new ImageIcon(getClass().getResource("/toolbarButtonGraphics/media/FastForward16.gif")));
    return button;
  }
}

class ScrollButton extends JButton {
    public ScrollButton(Icon icon) {
        super(icon);
        setContentAreaFilled(false);
        setBorder(new EmptyBorder(new Insets(0, 0, 0, 0)));
    }
    
    public boolean isFocusTraversable() {
        return false;
    }
}

Just like with the other UI delegates, customization means overriding some protected methods. In this case, you have to override the createNextMonthButton, createPreviousMonthButton, createNextYearButton and the createPreviousYearButton methods.