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, DefaultListCellRenderer or NokiaListCellRenderer.
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 would not do anything but still cost in terms of processing.
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 is 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. |
NokiaListCellRenderer is a list cell renderer implementation that caches information of truncated Strings. You can use it in place of DefaultListCellRenderer. If you have a list with lot of truncated Strings, create a new NokiaListCellRenderer and set it as the list cell renderer to get performance improvements. NokiaListCellRenderer is available as a template.
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 not 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 do not 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 behaviour 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 example
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.
Here is a simple code 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; }