As part of the GUI builder work we needed a way to customize rendering for a List but the renderer/model approach seemed impossible to adapt to a GUI builder (it seems the Swing GUI builders had a similar issue). Our solution was to introduce the GenericListCellRenderer which while introducing limitations and implementation requirements still manages to make life easier both in the GUI builder and outside of it.
A small refresher of the LWUIT List MVC approach for those who haven't read the links above:
A LWUIT list doesn't contain components but rather arbitrary data, this seems odd at first but makes perfect sense... If you want a list to contain components just use a Container.
The advantage of using a List in this way is that we can display it in many ways (e.g. fixed focus positions, horizontally etc.) and that we can have more than a million entries without performance overhead. We can also do some pretty nifty things like filter the list on the fly or fetch it dynamically from the internet as the user scrolls down the list.
To achieve these things the list uses two interfaces: ListModel and ListCellRenderer.
List model represents the data, its responsibility is to return the arbitrary object within the list at a given offset. Its second responsibility is to notify the list when the data changes so the list can refresh, think of it as an array of objects that can notify you when you get changes.
The list renderer is like a rubber stamp that knows how to draw an object from the model, its called many times per entry in an animated list and must be very fast. Unlike standard LWUIT components it is only used to draw the entry in the model and immediately discarded hence it has no memory overhead but if it takes too long to process a model value it can be a big bottleneck!
This is all very generic but a bit too much for most, doing a list "properly" requires some understanding. The main source of confusion for developers is the stateless nature of the list and transferal of state to the model (e.g. a checkbox list needs to listen to action events on the list and update the model in order for the renderer to display that state... Once you understand that its easy).
GenericListCellRenderer is a renderer designed to be as simple to use as a Component-Container hierarchy, we effectively crammed most of the common renderer use cases into one class. To enable that we need to know the content of the objects within the model, so the GenericListCellRenderer assumes the model contains only Hashtable objects. Since Hashtable's can contain arbitrary data the list model is still quite generic and allows storing application specific data, furthermore a Hashtable can still be derived and extended to provide domain specific business logic.
The GenericListCellRenderer accepts two container instances (more later on why at least two and not one) which it maps 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 (get/setName) and the key value within the Hashtable e.g.:
For a model that contains a Hashtable entry like this:
"Foo": "Bar"
"X": "Y"
"Not": "Applicable"
"Number": Integer(1)
A renderer will loop over the component hierarchy in the container searching for component's whose name matches Foo, X, Not and Number and assign to them the appropriate value. Notice that you can also use image objects as values and they will be assigned to labels as expected. However, you can't assign both an image and a text to a single label since the key will be taken. That isn't a big problem since two labels can be used quite easily in such a renderer.
To make matters even more attractive the renderer seamlessly supports list tickering when appropriate and if a CheckBox appears within the renderer it will toggle a boolean flag within the Hashtable seamlessly.
One issue that crops up with this approach is that if a value is missing from the hashtable it is treated as empty and the component is reset, this can pose an issue if we hardcode an image or text within the renderer and we don't want them replace (e.g. an arrow graphic). The solution for this is to name the component with Fixed in the end of the name e.g.: HardcodedIconFixed.
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.
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.
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. E.g. a Container might have a child label that has one style when the parent container is unselected and another when its 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 par of a renderer.
Last but not least, the generic list cell renderer accepts two or four instances of a Container rather than the obvious choice of accepting only one instance. This allows the renderer to treat the selected entry differently which is especially important to tickering although its also useful for fisheye. Since it might not be practical to seamlessly clone the Container for the renderer's needs LWUIT expects the developer to provide two separate instances, they can be identical in all respects but they must be separate instances for tickering to work. The renderer also allows for a fisheye effect where the selected entry is actually different from the unselected entry in its structure, it also allows for a pinstripe effect where odd/even rows have different styles (this is accomplished by providing 4 instances of the containers selected/unselected for odd/even).
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 and can be quite useful at that.
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;
}
hi,
ReplyDeleteim having an issue where despite the display.getinstance.getcurrent pointing to next form the display is still the present form. i mailed you my issue as well .. dunno if i got ur email right.
some error in UI (Revision 1288)
ReplyDeleterun time
Display.init(this)
TRACE: , startApp threw an Exception
java.lang.NoClassDefFoundError: com/sun/lwuit/impl/ImplementationFactory
at com.sun.lwuit.Display.init(Display.java:400)
at IslamProphet.startApp(IslamProphet.java:52)
Hi
ReplyDeleteWhat I want to know is that how to make a multiline list item and also in the same time how to control what appear on the first line and what appear on the second line (to do that I thought that I could use a HTMLComponent) but the problem is that when I did that with the generic list cell render the list item was blank) so how could I achieve that
And Thanks in advance
Please and then, how do I extract the selected item?.
ReplyDelete