Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Swing Chapter 9. (The basics) Combo Boxes. Easy for reading, Click here!

Custom Search
Swing Chapter 9. (The basics) Combo Boxes. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 3/5 



Previous Page Previous Page (2/5) - Next Page (4/5) Next Page
Subpages: 1. Combo Boxes: JComboBox 
2.
Basic JComboBox example 
3.
Custom model and renderer 
4. Comboboxes with memory 
5. Custom editing 

9.3    Custom model and renderer

Ambitious Swing developers may want to provide custom rendering in combo boxes to display structured data in the drop-down list. Different levels of structure can be identified by differing left margins and icons, just as is done in trees (which we will study in chapter 17). Such complex combo boxes can enhance functionality and provide a more sophisticated appearance.

In this section we will show how to merge the model and trim combo boxes from the previous section into a single combo box. To differentiate between model and trim items in the drop-down list, we can use different left margins and different icons for each. Our list should looke something like this:

Nissan Maxima

     GXE

     SE

     GLE

We also need to prevent the user from selecting models (e.g. "Nissan Maxima" above), since they do not provide complete information about a specific car, and only serve as separators between sets of trims.

Note: The hierarchical list organization shown here can easily be extended for use in a JList, and can handle an arbitrary number of levels. We only use two levels in this example, however, the design does not limit us to this.

Figure 9.2 JComboBox with a custom model and a custom hierarchical rendering scheme.

<<file figure9-2.gif>>

The Code: ComboBox2.java

see \Chapter9\2

// Unchanged code from section 9.2

class CarPanel extends JPanel

{

  protected JComboBox m_cbCars;

  protected JLabel m_txtModel;

  protected JLabel m_lblImg;

  protected JLabel m_lblMSRP;

  protected JLabel m_lblInvoice;

  protected JLabel m_lblEngine;

  public CarPanel(String title, Vector cars) {

    super();

    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

    setBorder(new TitledBorder(new EtchedBorder(), title));

    JPanel p = new JPanel();

    m_txtModel = new JLabel("");

    m_txtModel.setForeground(Color.black);

    p.add(m_txtModel);

    add(p);

    p = new JPanel();

    p.add(new JLabel("Car:"));

    CarComboBoxModel model = new CarComboBoxModel(cars);

    m_cbCars = new JComboBox(model);

    m_cbCars.setRenderer(new IconComboRenderer());

    ActionListener lst = new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        ListData data = (ListData)m_cbCars.getSelectedItem();

        Object obj = data.getObject();

        if (obj instanceof Trim)

          showTrim((Trim)obj);

      }

    };

    m_cbCars.addActionListener(lst);

    p.add(m_cbCars);

    add(p);

    //Unchanged code from section 9.2

  }

  public synchronized void selectCar(Car car) {

    for (int k=0; k < m_cbCars.getItemCount(); k++) {

      ListData obj = (ListData)m_cbCars.getItemAt(k);

      if (obj.getObject() == car) {

        m_cbCars.setSelectedItem(obj);

        break;

      }

    }

  }

  public synchronized void showTrim(Trim trim) {

    Car car = trim.getCar();

    m_txtModel.setText(car.toString());

    m_lblImg.setIcon(car.getIcon());

    m_lblMSRP.setText("$" + trim.getMSRP());

    m_lblInvoice.setText("$" + trim.getInvoice());

    m_lblEngine.setText(trim.getEngine());

  }

}

class ListData

{

  protected Icon    m_icon;

  protected int     m_index;

  protected boolean m_selectable;

  protected Object  m_data;

  public ListData(Icon icon, int index, boolean selectable,

   Object data) {

    m_icon = icon;

    m_index = index;

    m_selectable = selectable;

    m_data = data;

  }

  public Icon getIcon() { return m_icon; }

  public int getIndex() { return m_index; }

  public boolean isSelectable() { return m_selectable; }

  public Object getObject() { return m_data; }

  public String toString() { return m_data.toString(); }

}

class CarComboBoxModel extends DefaultComboBoxModel

{

  public static final ImageIcon ICON_CAR =

    new ImageIcon("car.gif");

  public static final ImageIcon ICON_TRIM =

    new ImageIcon("trim.gif");

  public CarComboBoxModel(Vector cars) {

    for (int k=0; k<cars.size(); k++) {

      Car car = (Car)cars.elementAt(k);

      addElement(new ListData(ICON_CAR, 0, false, car));

      Vector v = car.getTrims();

      for (int i=0; i < v.size(); i++) {

        Trim trim = (Trim)v.elementAt(i);

        addElement(new ListData(ICON_TRIM, 1, true, trim));

      }

    }

  }

  // This method only allows trims to be selected

  public void setSelectedItem(Object item) {

    if (item instanceof ListData) {

      ListData ldata = (ListData)item;

      if (!ldata.isSelectable()) {

        Object newItem = null;

        int index = getIndexOf(item);

        for (int k = index + 1; k < getSize(); k++) {

          Object item1 = getElementAt(k);

          if (item1 instanceof ListData) {

            ListData ldata1 = (ListData)item1;

            if (!ldata1.isSelectable())

              continue;

          }

          newItem = item1;

          break;

        }

        if (newItem==null)

          return;        // Selection failed

        item = newItem;

      }

    }

    super.setSelectedItem(item);

  }

}

class IconComboRenderer extends JLabel implements ListCellRenderer

{

  public static final int OFFSET = 16;

  protected Color m_textSelectionColor = Color.white;

  protected Color m_textNonSelectionColor = Color.black;

  protected Color m_textNonselectableColor = Color.gray;

  protected Color m_bkSelectionColor = new Color(0, 0, 128);

  protected Color m_bkNonSelectionColor = Color.white;

  protected Color m_borderSelectionColor = Color.yellow;

  protected Color  m_textColor;

  protected Color  m_bkColor;

  protected boolean m_hasFocus;

  protected Border[] m_borders;

  public IconComboRenderer() {

    super();

    m_textColor = m_textNonSelectionColor;

    m_bkColor = m_bkNonSelectionColor;

    m_borders = new Border[20];

    for (int k=0; k < m_borders.length; k++)

      m_borders[k] = new EmptyBorder(0, OFFSET * k, 0, 0);

    setOpaque(false);

  }

  public Component getListCellRendererComponent(JList list,

   Object obj, int row, boolean sel, boolean hasFocus) {

    if (obj == null)

      return this;

    setText(obj.toString());

    boolean selectable = true;

    if (obj instanceof ListData) {

      ListData ldata = (ListData)obj;

      selectable = ldata.isSelectable();

      setIcon(ldata.getIcon());

      int index = 0;

      if (row >= 0)    // no offset for editor (row=-1)

        index = ldata.getIndex();

      Border b = (index < m_borders.length ? m_borders[index] :

        new EmptyBorder(0, OFFSET * index, 0, 0));

      setBorder(b);

    }

    else

      setIcon(null);

    setFont(list.getFont());

    m_textColor = (sel ? m_textSelectionColor :

      (selectable ? m_textNonSelectionColor :

      m_textNonselectableColor));

    m_bkColor = (sel ? m_bkSelectionColor :

      m_bkNonSelectionColor);

    m_hasFocus = hasFocus;

    return this;

  }

  public void paint (Graphics g) {

    Icon icon = getIcon();

    Border b = getBorder();

    g.setColor(m_bkNonSelectionColor);

    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor(m_bkColor);

    int offset = 0;

    if(icon != null && getText() != null) {

      Insets ins = getInsets();

      offset = ins.left + icon.getIconWidth() + getIconTextGap();

    }

    g.fillRect(offset, 0, getWidth() - 1 - offset,

      getHeight() - 1);

    if (m_hasFocus) {

      g.setColor(m_borderSelectionColor);

      g.drawRect(offset, 0, getWidth()-1-offset, getHeight()-1);

    }

    setForeground(m_textColor);

    setBackground(m_bkColor);

    super.paint(g);

  }

}

Understanding the Code

Class CarPanel

Classes ComboBox2 (formerly ComboBox1), Car, and Trim remain unchanged in this example, so we'll start from the CarPanel class. Compared to the example in the previous section, we've removed combo box m_cbTrims, and added JLabel m_txtModel, which is used to display the current model's name (when the combo box popup is hidden, the user can see only the selected trim; so we need to display the corresponding model name separately). Curiously, the constructor of the CarPanel class places this label component in its own JPanel (using its default FlowLayout) to ensure it's location in the center of the base panel.

Note: The problem is that JLabel m_txtModel has a variable length, and the BoxLayout which manages CarPanel cannot dynamically center this component correctly. By placing this label in a FlowLayout panel it will always be centered.

The single combo box , m_cbCars, has a bit in common with the component of the same name in the previous example. First it receives a custom model, an instance of the CarComboBoxModel class, which will be described below. It also receives a custom renderer, an instance of the IconComboRenderer class, also described below.

The combo box is populated by both Car and Trim instances encapsulated in ListData objects (see below). This requires some changes in the actionPerformed() method which handles combo box   selection. First we extract the data object from the selected ListData instance by calling the getObject() method. If this call returns a Trim object (as it should, since Cars cannot be selected), we call the showTrim() method to display the selected data.

Method selectCar() has been modified. As we mentioned above, our combo box now holds ListData objects, so we cannot pass a Car object as a parameter to the setSelectedItem() method. Instead we have to examine in turn all items in the combo box, cast them to ListData objects, and verify that the encapsulated data object is equal to the given Car instance. The == operator verifies that the address in memory of the object corresponding to the combo box is the same as the address of the given object. This assumes that the Car object passed to selectCar() is taken from the collection of objects used to populate this combo box. (To avoid this limitation we could alternatively implement an equals() method in the Car class.)

Method showTrim() now does the job of displaying the model data as well as the trim data. To do this we obtain a parent Car instance for a given Trim and display the model's name and icon. The rest of this method remains unchanged.

Class ListData

This class encapsulates the data object to be rendered in the combo box and adds new attributes for our rendering needs.

Instance variables:

m_icon Icon: icon associated with the data object.

m_index int: item's index which determines the left margin (i.e. the hierarchical level).

m_selectable boolean: flag indicating that this item can be selected.

m_data Object: encapsulated data object.

All variables are filled with parameters passed to the constructor. The rest of the ListData class represents four getXX() methods and a toString() method, which delegate calls to the m_data object.

Class CarComboBoxModel

This class extends DefaultComboBoxModel to serve as a data model for our combo box . First it creates two static ImageIcons to represent model and trim. The constructor takes a Vector of Car instances and converts them and their trims into a linear sequence of ListData objects. Each Car object is encapsulated in a ListData instance with an ICON_CAR icon, index set to 0, and m_selectable flag set to false. Each Trim object is encapsulated in a ListData instance with ICON_TRIM icon, index set to 1, and m_selectable flag set to true.

These manipulations could have been done without implementing a custom ComboBoxModel, of course. The real reason we do implement a custom model is to override the setSelectedItem() method to control item selection in the combo box. As we learned above, only ListData instances with the m_selectable flag set to true should be selectable. To achieve this goal, the overridden setSelectedItem() method casts the selected object to a ListData instance and examines its selection property using isSelectable().

If isSelectable() returns false, a special action needs to be handled to move the selection to the first item following this item for which isSelectable() returns true. If no such item can be found our setSelectedItem() method returns and the selection in the combo box remains unchanged. Otherwise the item variable receives a new value which is finally passed to the setSelectedItem() implementation of the superclass DefaultComboBoxModel.

Note: You may notice that the selectCar() method discussed above selects a Car instance which cannot be selected. This internally triggers a call to the setSelectedItem() of the combo box model, which shifts the selection to the first available Trim item. You can verify this when running the example.

Class IconComboRenderer

This class extends JLabel and implements the ListCellRenderer interface to serve as a custom combo box renderer.

Instance variables:

int OFFSET: offset in pixels of image and text (different for cars and trims).

Color m_textColor: current text color.

Color m_bkColor: current background color.

boolean m_hasFocus: flag indicating whether this item has focus.

Border[] m_borders: an array of borders used for this component.

The constructor of the IconComboRenderer class initializes these variables. EmptyBorders are used to provide left margins while rendering components of the drop-down list. To avoid generation of numerous temporary objects, an array of 20 Borders is prepared with increasing left offsets corresponding to array index (incremented by OFFSET). This provides us with a set of different borders to use for white space in representing data at 20 distinct hierarchical levels.

Note: Even though we only use two levels in this example, IconComboRenderer has been designed for maximum reusability. 20 levels should be enough for most hierarchies, but if more levels are necessary we've designed getListCellRendererComponent() (see below) to create a new EmptyBorder in the event that more than 20 levels are used.

The opaque property is set to false because we intend to draw the background ourselves.

Method getListCellRendererComponent() is called prior to the painting of each cell in the drop-down list. We first set this component's text to that of the given object (passed as parameter). Then, if the object is an instance of ListData, we set the icon and left margin by using the appropriate EmptyBorder from the previously prepared array (based on the given ListData's m_index property--if the index is greater than the). Note that a call to this method with row=-1 will be invoked prior to the rendering of the combo box editor, which is the part of the combo box that is always visible (see 9.1). In this case we don't need to use any border offset. Offset only makes sense when there are hierarchical differences between items in the list, not when an item is rendered alone.

The rest of the getListCellRendererComponent() method determines the background and foreground colors to use, based on whether is selected and selectable, and stores them in instance variables for use within the paint() method. Non-selectable items receive their own foreground to distinguish them from selectable items.

The paint() method performs a bit of rendering before invoking the super-class implementation. It fills the background with the stored m_bkColor (from above) excluding the icon's area (note that the left margin is already taken into account by the component's Border). It also draws a border-like rectangle if the component currently has the focus. This method then ends with a call to its super-class's paint() method which takes responsibility for painting the label text and icon

Running the Code

Figure 9.2 shows our hierarchical drop-down list in action. Note that models and trim lines can be easily differentiated because of the varying icons and offsets. In addition, models have a gray foreground to imply that they cannot be selected.

This implementation is more user-friendly than the previous example because it displays all available data in a single drop-down list. Try selecting different trims and note how this changes data for both the model and trim information labels. Try selecting a model and note that it will result in the selection of the first trim of that model.

UI Guideline : Improved Usability

From a usability perspective the solution in fig 9.2 is an improvement over the one presented in fig 9.1. By using a combobox with a hierarchical data model, the designer has reduced the data entry to a single selection and has presented the information in an accessible and logical manner which also produces a visually cleaner result.

Further improvements could be made here by sorting the hierarchical data. In this example it would seem appropriate to sort in a two tiered fashion: alphabetically by manufacturer; and alphabetically by model. Thus Toyota would come after Ford and Toyota Corolla would come after Toyota Camry.

This is an excellent example of how the programmer can improve UI Design and Usability by doing additional work to make the User's Goal easier to achieve.



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy