Unfortunately changing the renderer API isn't practical at this stage but we simplified a lot thanks to the GenericListCellRenderer which powers GUI builder list renderers. We simplified this further with the MultiList component which has a default renderer that's pretty powerful to begin with.
However, this simplicity has resulted in developers using lists without quite understanding why they behave in this way. In a normal container all components are kept in a Tree like hierarchy that we can render (essentially similar to DOM). A list however doesn't contain any components within it so it can hold a million entries with the same overhead it would have when holding 100 entries.
It works like this:
- The EDT (event dispatch thread) asks the List to paint itself.
- The List asks the ListModel for the values of the currently visible entries.
- The List asks the renderer for a component representing every value (the same instance is recycled and discarded).
- This List paints the renderer as a rubber stamp for every single value.
So if the model has 1m entries we don't need to create 1m components since we will reuse the renderer for every entry. We also don't render elements that are invisible.
There are problems with this model though:
- Since the renderer is "stamped" (drawn then used for a different value) adding a listener to a component within the list won't do what you expect.
- Obviously individual elements within the list entry can't get focus and can't be edited
- Within a List all entries have to be the exact same width/height. Otherwise its very expensive for us to calculate the list offset. ContainerList allows for variable height list but its performance/functionality is equivalent to Container so the benefits aren't great.
- The performance of the model & the renderer must be very high otherwise the whole list will be impacted.
- Its hard to create animations (since there is no state).
You might be asking yourself: Why should I use a List? Why not just use a List or a Container with BoxLayout Y?
That's actually our recommendation for most cases, List is often too much of a hassle for some use cases and we try to avoid it in many applications. It is now also possible to create long scrolling containers relatively easily although you might see performance degrading after a while.
However List does have some serious advantages:
- Scale - it scales well to huge lists and maintains speed.
- Flexibility - list can be flicked to horizontal mode, center selection behavior and other such capabilities.
- MVC - the list makes working with a data model really easy, if you are well versed with MVC this could be a very powerful
So assuming you pick list lets see how you can make better use of it.
How do you deal with events on a button click within a list renderer?
So assuming I have a list renderer that has a button named X on the the side, dealing with this differs between a GUI builder app and a handcoded app.
For a GUI builder app use the standard action listener for the list and within the action listener just write:
If you are building a handcoded renderer this is a bit harder:
There are a few other features in the generic list cell renderer that aren't well documented:
- The UIID of the renderer is based on the UIID of the renderer component we also add a focus component to the list whose UIID is based on: selected.getUIID() + "Focus". So if your selected renderer container has the UIID MyRenderer, then you focus component will have the UIID MyRendererFocus.
- Items are always overriden by their hashtable/map value even when they are missing. So if you place an icon in the renderer but don't put the icon value in the hashtable model it will be removed.
A solution is to just name the component with the word fixed in the end (case insensitive) as in iconFixed. This means that the value you give the icon in the renderer won't change.
- You can disable/enable entries within the list by using map.put(GenericListCellRenderer.ENABLED, Boolean.FALSE). Notice that once you do this you must do this for all the entries otherwise the renderers "rubber stamp" behavior will repeat the status of the last entry.
- You can implement a "Select All" entry for a checkbox list by using map.put(GenericListCellRenderer.SELECT_ALL_FLAG, Boolean.TRUE). This is pretty useful if you have a checkbox list.
- You can place the entry number within the list by creating a component named $number. It will start with 1 as the offset and not with 0.
- If you want to provide a different value when an entry is selected (e.g. different image when clicked) you can use # in front of the name e.g. to allow Icon to have a different image when selected put a value into #Icon.