Skip Headers
Lightweight UI Toolkit Developer's Guide
Release 1.5
E23376-03
  Go To Table Of Contents
Contents
Go To Index
Index

Previous
Previous
 
Next
Next
 

3 Using Lists

Because screen size is limited, lists are the most common basic UI widget on devices. A List presents the user with a group of items displayed in a single column. The set of elements is rendered using a ListCellRenderer and is extracted using the ListModel. Swing's Model/View/Controller architecture (MVC) makes it possible for a list to represent many UI concepts ranging from a carousel to a To-Do checklist. A list component is relatively simple. It invokes the model in order to extract the displayed or selected information and invokes the cell renderer to show it to the user. The list class itself is completely decoupled from everything, so you can extract its content from any source (for example, the network, storage etcetera) and display the information in any form (for example, Checkboxes, Strings, Icons, and so forth).

Initializing a List

You can create a list in one of four ways:

List()

Creates a new instance of List with an empty default model.

List(ListModel model)

Creates a new instance of List with the given model.

List(Object[] items)

Creates a new instance of List with an array of Objects that are placed into the list model.

List(Vector items)

Creates a new instance of List where a set of items are placed into the list model.


Creating a Model

There are two ways to create a list model:

ListModel

Implement the list model interface (use a general purpose implementation of the list model interface derived from the DefaultListModel)

DefaultListModel

Everything is taken care of for you.


ListModel

Represents the data structure of the list, thus allowing a list to represent any potential data source by referencing different implementations of this interface. For example, a list model can be implemented in such a way that it retrieves data directly from storage (although caching is recommended). It is the responsibility of the list to notify observers (specifically the view List of any changes to its state (items removed, added, or changed, and so forth) thus the data is updated on the view.

DefaultListModel

The following code demonstrates using the DefaultListModel class with a vector of elements.

// Create a set of items
String[] items = { "Red", "Blue", "Green", "Yellow" };
 
// Initialize a default list model with “item” inside
DefaultListModel myListModel = new DefaultListModel(items);
 
// Creating a List with “myListModel”

List Cell Renderer

A list uses an object called a cell renderer to display each of its items. The default cell renderer knows how to display strings and icons and it displays Objects by invoking toString. If you want to change the way the default renderer display icons or strings, or if you want behavior different than what is provided by toString, you can implement a custom cell renderer. You can create a list renderer using ListCellRenderer or DefaultListCellRenderer:

ListCellRenderer

ListCellRenderer is a "rubber stamp" tool that allows you to extract a renderer instance (often the same component instance for all invocations) that is initialized to the value of the current item. The renderer instance is used to paint the list and is discarded when the list is complete.

An instance of a renderer can be developed as follows:

public class MyYesNoRenderer extends Label implements ListCellRenderer {
   public Component getListCellRendererComponent(List list,
                      Object value, int index, boolean isSelected) {
      if( ((Boolean)value).booleanValue() ) {
        setText("Yes");
      } else {
           setText("No");
      }
        return this;
   }
 
   public Component getListFocusComponent(List list) {
      Label label = new label("");
      label.getStyle().setBgTransparency(100);
 
      return label;
   }
}

It is best that the component whose values are manipulated does not support features such as repaint(). This is accomplished by overriding repaint in the subclass with an empty implementation. This is advised for performance reasons, otherwise every change made to the component might trigger a repaint that wouldn't do anything but still cost in terms of processing.

DefaultListCellRenderer

The DefaultListCellRender is the default implementation of the renderer based on a Label and the ListCellRenderer interface.

getListCellRendererComponent()

Returns a component instance that is already set to renderer "value". While it is not a requirement, many renderers often derive from a component (such as a label) and return "this".

getListFocusComponent()

Returns a component instance that paints the list focus item. When the selection moves, this component is drawn above the list items. It's best to give some level of transparency (see code example in ListCellRenderer). Once the focused item reaches the cell location then this Component is drawn under the selected item.

Note: To emulate this animation, call List.setSmoothScrolling(true). This method is optional an implementation can choose to return null


Adding Items to and Removing Items From a List

You can add items to a list in one of two ways. The first way is to create a ListModel and add it to the list, either when initiating a List or using the method setModel(ListModel model). To remove an item or all items from a List, use removeItem(int index) or removeAll() methods. For example:

// Adding to a list using above DefaultListModel snipped code or:
....
myListModel.addItem(“New Item”);
 
// Removing is done by
....
myListModel.removeItem(index);
// or
myListModel.removeAll();

List Events

Two types of events are supported here, ActionEvent and SelectionsListener in addition to addFocusListener(FocusListener l) that is inherited from Component. ActionEvent binds a listener to the user selection action, and the SelectionListener is bound to the List model selection listener. The listener bindings mean you can track changes in values inside the Model.

Fixed Selection Feature

The fixed selection feature supports a dynamic versus static item movement in a List. In a Java SE environment the list items are typically static and the selection indicator travels up and down the list, highlighting the currently selected item. The Lightweight UI Toolkit introduces a new animation feature that lets the selection be static while the items move dynamically up and down. To indicate the fixed selection type, use setFixedSelection(int fixedSelection) where fixedSelection can be one of the following:

FIXED_NONE

Behave as the normal (Java SE) List behaves. List items are static and the selection indicator travels up and down the list, highlighting the currently selected item.

FIXED_TRAIL

The last visible item in the list is static and list items move up and down.

FIXED_LEAD

The first item in the list is static and list items move up and down.

FIXED_CENTER

The middle item in the list is static and list items are move up and down.


Tickers in List

Because list items are essentially rendered as a rubber stamp they can't be treated as typical LWUIT components. Things such as binding event listeners to the components in the list won't work since the list reuses the same component to draw all the entries.

Features such as tickering an individual cell are often requested and the solution isn't trivial because what we need to do is essentially "ticker the List" not the renderer.

The sample below tickers a renderer by registering itself as an animation in the parent form and calling the list's repaint method to ticker. Notice that it has a separate entry for the selected list item otherwise the entire content of the list would constantly ticker.

Example 3-1 Tickering a Renderer

class TickerRenderer extends DefaultListCellRenderer {
   private DefaultListCellRenderer selectedRenderer = new
            DefaultListCellRenderer(false);
   private List parentList;   public TickerRenderer() 
      super(false);
   }
 
   public boolean animate() {
      if(parentList != null && parentList.getComponentForm() != null) {
         if(selectedRenderer.isTickerRunning()) {
            if(selectedRenderer.animate()) {
               parentList.repaint();
            }
         }
      }
      return super.animate()
  }
   public Component getListCellRendererComponent(List list, Object value, int
                      index, boolean isSelected) {
       if(isSelected) {          selectedRenderer.getListCellRendererComponent(list, value, index,
                                                             isSelected);
          // sometimes the list asks for a dummy selected value for size 
          // calculations and this might break the tickering state
          if(index == list.getSelectedIndex()) {
              if(selectedRenderer.shouldTickerStart()) {
                  if(!selectedRenderer.isTickerRunning()) {
                      parentList = list;
                      list.getComponentForm().registerAnimated(this);
                    selectedRenderer.startTicker(UIManager.getInstance().
                                      getLookAndFeel().getTickerSpeed(), true);
               }           } else {
               if(selectedRenderer.isTickerRunning()) {
                 selectedRenderer.stopTicker();
               }
             }
          }
          return selectedRenderer;
      } else {
          return super.getListCellRendererComponent(list,value,index,                                                     isSelected);
      }
   }
}

Generic List Cell Renderer

GenericListCellRenderer is a renderer designed to be as simple to use as a Component-Container hierarchy. This single class supports most of the common use cases. To GenericListCellRenderer assumes the model contains only Hashtable objects. Since Hashtables can contain arbitrary data, the list model is still quite generic and allows application-specific data storage. Furthermore, a Hashtable can still be derived and extended to provide domain-specific business logic.

Mapping Components to Hashtable Entries

The GenericListCellRenderer accepts two Container instances and maps them to individual Hashtable entries within the model by finding the appropriate components within the given Container hierarchy.

Components are mapped to the Hashtable entries based on the name property of the component (getName , setName) and the key value within the Hashtable.

Assume a model that contains a Hashtable entry like this:

"Foo": "Bar"
"X": "Y"
"Not": "Applicable"
"Number": Integer(1)

In this model a renderer loops over the component hierarchy in the Container searching for components whose name matches Foo, X, Not and Number and assigns the appropriate value to them. Notice that if you use image objects as values they are assigned to labels as expected. You can't assign both an image and text to a single label because a key takes only one object. Two labels can be used quite easily in this case.

Even better, the renderer supports list tickering when appropriate, and if a CheckBox appears within the renderer it seamlessly toggles a boolean flag within the Hashtable.

If a value is missing from the Hashtable it is treated as empty and the component is reset. This is an issue if you hardcode an image or text within the renderer and you don't want it replaced. To ensure a component is preserved, use the setName property to append Fixed to the name. For example: Given an address, specify:

address.setName("addressFixed");

Naming a component within the renderer with $number will automatically set it as a counter component for the offset of the component within the list. For example:

c.mycomponent("Idate$1");

Styling the GenericListCellRenderer is slightly different. The renderer uses the UIID of the Container passed to the generic list cell renderer and the background focus uses that same UIID with the word "Focus" appended. For example:

c.setUIID("ListRendererFocused");

Focus for Tickering and Fisheye

It is important to notice that the generic list cell renderer will grant focus to the child components of the selected entry if they are focusable, thus changing the style of said entries. For example, a Container might have a child label that has one style when the parent Container is unselected and another when it is selected (focused). This can be easily achieved by defining the label as focusable. Notice that the component will never receive direct focus since it is still a part of a renderer.

Because the generic list cell renderer accepts two or four instances of a Container, the renderer can treat the selected entry differently which is very important for tickering.

It might not be practical to seamlessly clone the Container instances for the renderer's needs, so LWUIT expects the developer to provide two separate instances. This is essential for tickering; there must be separate instances, even if they are identical.

The renderer also supports a fisheye effect in which the selected entry is actually different from the unselected entry in its structure. This behavior supports a pinstripe effect where odd and even rows can have different styles. For example to get a pinstripe effect, provide 4 instances of the Containers and selected and unselected values for odd and even rows.

Hashtable Sample

The best way to learn about the generic list cell renderer and the Hashtable model is by playing with them in the GUI builder, however they can be used in code without any dependency on the GUI builder.

Example 3-2 List With Checkboxes

Here is a simple sample for a list with checkboxes that get updated automatically:

List list = new List(createGenericListCellRendererModelData());
list.setRenderer(new GenericListCellRenderer(createGenericRendererContainer(), createGenericRendererContainer()));
private Container createGenericRendererContainer() {
   Container c = new Container(new BorderLayout());
   c.setUIID("ListRenderer");
   Label name = new Label();
   name.setFocusable(true);
   name.setName("Name");
   c.addComponent(BorderLayout.CENTER, name);    
   Label surname = new Label();
   surname.setFocusable(true); 
   surname.setName("Surname");
   c.addComponent(BorderLayout.SOUTH, surname);
   CheckBox selected = new CheckBox();
   selected.setName("Selected");
   selected.setFocusable(true);
   c.addComponent(BorderLayout.WEST, selected);
   return c;}
private Hashtable[] createGenericListCellRendererModelData() {
   Hashtable[] data = new Hashtable[5];
   data[0] = new Hashtable();
   data[0].put("Name", "Shai");
   data[0].put("Surname", "Almog");
   data[0].put("Selected", Boolean.TRUE);
   data[1] = new Hashtable();
   data[1].put("Name", "Chen");
   data[1].put("Surname", "Fishbein");
   data[1].put("Selected", Boolean.TRUE);
   data[2] = new Hashtable();
   data[2].put("Name", "Ofir");
   data[2].put("Surname", "Leitner");
   data[3] = new Hashtable();
   data[3].put("Name", "Yaniv");
   data[3].put("Surname", "Vakarat");
   data[4] = new Hashtable();
   data[4].put("Name", "Meirav");
   data[4].put("Surname", "Nachmanovitch");
   return data;}