ListControl can display any type of data, and it permits the editing of data. The user can select one or many items presented in the list, add items, delete items, edit items, scroll the list box, navigate using key strokes, select one or several items, and so on.
Because ListControl has so many behaviors, this chapter narrows its focus to what happens when the list control is temporarily obscured by another window and then becomes wholly visible again. Whenever such events occur, the area that becomes visible again is repainted. By examining the code, you'll be able to see the list control's model-view architecture in action:
When you installed JBuilder, you installed the source code for ListControl and all the interfaces and classes that are used to create it. You will find it helpful to refer to that source code while reading this chapter. Start with jbcl.control.ListControl.
This chapter includes these topics:
The WritableVectorModel interface extends VectorModel and provides the capability of writing to a vector as well as reading from it. Any class can implement either of these interfaces in any way required. The BasicVectorContainer class in the model package implements both interfaces and uses a jgl.Array to store the actual data.
For more information about the models in JBCL, see Models in JBCL.
ListView asks the VectorModel how many items it has by calling VectorModel.getCount() to determine how much of the screen it needs to display the data. When ListView needs to paint itself, it calculates from the clip rectangle which items need to be repainted. Using a for loop, it cycles through each item in the rectangle and retrieves each data object from the model by calling VectorModel.get(int).
If the passed data object is of type Object, how does the ListView know how to paint the items? Suppose the item at the third position or index is a CORBA busines object that consumes four megabytes of RAM and it's followed by a String, and finally a unique "Donkey" object at the fifth position. ListView solves this problem by delegating the painting tasks to item painters.
The view package contains several item painters such as TextItemPainter, FocusableItemPainter, and so on. Because ItemPainter is an interface, any class that implements it can paint items in any JBCL component. The item painter can paint an item any way that is needed, using all, some, or none of the passed arguments in its the paint() method.
For example, the paint() method of TextItemPainter extracts a String from the data object using the toString() method, obtains the foreground color, font, and alignment settings from the ItemPaintSite, then draws the characters appropriately in the specified rectangle. The paint() method of FocusableItemPainter ignores the data object, however, and uses the state information instead. If the FOCUSED state is set, paint() draws a dotted rectangle around the perimeter of the specified rectangle.
You can nest item painters. For example, FocusItemPainter has an ItemPainter property that allows you to put another item painter, or a chain of item painters, inside it. The paint() call to FocusableItemPainter is forwarded to its nested item painter, then FocusableItemPainter paints the focus rectangle over it.
A CompositeItemPainter contains two item painters that can be oriented either vertically or horizontally. Its paint() method breaks the rectangle passed to it into two rectangles and calls the paint() methods of its nested item painters. You can use CompositeItemPainter to put glyphs next to the text in a tree, for eample.
The arguments to the paint() method don't include the address of the information, such as an index to a particular data item held in the VectorModel. This seeming ommission makes it possible to use ItemPainter implementations for all model types, not just for vectors. It is the view manager that determines which item is painted with which item painter.
For more information about the item painters in the view package, see Item painters and item editors.
There are four view manager interfaces that extend ViewManger -- one for each model type. The ListView component of ListControl implements the VectorViewManager interface. The four different view manager interfaces differ only in the address information passed in their methods.
The model package includes a BasicViewManager that implements all four view manager interfaces. BasicViewManager has only one item painter that is always returned, regardless of the address information. It is the default view manager for all the JBCL components. You can, however, use your own view manager instead to return different item painters based on any of the information passed to the getPainter() method.
Item editors have a startEdit() method that has these three arguments:
When ListView detects the pressing of F2 or a double-click on an item, it asks the VectorModel for the data at that index, asks the view manager for an editor for that item, and then starts the edit session using an item editor. There are several item editors in the view package that you can use. Or you can create your own to meet your special needs.
Besides startEdit(), these are some of the other methods to implement in an item editor:
For more information about the item editors in JBCL, see Item painters and item editors.
ListControl extends the ListView class, which extends the ScrollPane class. ScrollPane provides scrolling behavior to the control. By looking at the constructor of ListView, you can see that something called the core is added to ListView:
public ListView() { super(); add(core); }
core is an instance of ListCore. This is the definition of core:
private ListCore core = new ListCore(this);
The ListCore class extends BeanPanel, which is a panel that is commonly used as a superclass for views and controls. BeanPanel classes subdispatch focus, key, and mouse events, manage action listeners, and manage tab/focus awareness.
ListCore contains most of the essential code for ListView. Many of the accessor methods for properties found in ListView simply call methods of the same name in ListCore, delegating their behavior to ListCore. Most of the code presented in this chapter comes from ListCore.
So far, nothing presented here is specific to model-view architecture, but is the underlying structure of ListControl.
public boolean isReadOnly() { return core.isReadOnly(); } public void setReadOnly(boolean ro) { core.setReadOnly(ro); }
ListCore also provides all the methods that implement VectorView so that ListCore can perform all the tasks ListView delegates to it. For example, here are the accessor methods for the readOnly property, which the readOnly accessor methods in ListView call:
public boolean isReadOnly() { return readOnly ? true : writeModel == null; } public void setReadOnly(boolean ro) { readOnly = ro; }
public ListControl(ScrollPane host) { super(); buildStringList(null); . . . } private void buildStringList(String[] newItems) { . . . super.setModel(new BasicVectorContainer(newItems)); . . . }
buildStringList() calls the setModel() method in ListView, instantiating a BasicVectorContainer object, an implementation of a writable vector model. BasicVectorContainer holds vector data and has the methods to count, find, add, and remove items from the vector.
buildStringList() also creates a BasicViewManager object, passing it a FocusableItemPainter object. A FocusableItemPainter can paint an item that can receive the focus and therefore has a focus rectangle around it. As FocusableItemPainter is created, it is passed a SelectableItemPainter object and a TextItemEditor object, the specific item painter and item editor that will paint and edit the items in the list control:
private void buildStringList(String[] newItems) { . . . super.setViewManager(new BasicViewManager( new FocusableItemPainter( new SelectableItemPainter(new TextItemPainter(getAlignment(), getItemMargins())), new TextItemEditor(getAlignment(), getItemMargins()))); . . . }
So now ListControl, ListView, and ListCore have a model to hold the data, an item painter to display it, and an item editor to edit it. ListView and ListCore are the view, and they both implement the VectorView interface. After the constructor of ListControl finishes, all the elements of a model-view component exist: the view, a model object, a view manager, and item painter and editor objects.
The code that follows here is a simplified version of the actual paint() method of ListCore. Some of the more complex details are omitted to make it easier for you to see how the model-view architecture of ListControl works when ListControl receives the command to repaint itself. If you would like to see the actual paint() method, examine the full source code of ListCore. Here is the beginning of ListCore.paint():
private Image canvas; public void paint(Graphics pg) { super.paint(pg); . . .
AWT passes the graphics object on which to paint to the paint() method as the value of the pg parameter. The first task paint() undertakes is to call the paint() method of ListCore's superclass. All paint() methods should begin this way. After the superclass's paint() method is called, ListCore.paint() calculates which data items in the list need to be repainted:
Rectangle clip = pg.getClipBounds(); int first = hitTest(clip.y); int last = hitTest(clip.y + clip.height); Rectangle r = getItemRect(first);
pg.getClipBounds() returns the clipped rectangle. The first variable holds the index of the first data item in the clipped rectangle, and last holds the index of the last data item. Using the index value of the first data item, getItemRect() returns the rectangle that surrounds the first data item. So far, the list control has no idea what the value of the data is or even what type of data it is. It only "knows" about the area needed to repaint the data in, which is the size of r.
Now paint() enters a for loop that cycles through each of the items in the clip rectangle and repaints them. To eliminate some of the complex details, all the rectangles the items are painted in are the same size in this code example. The actual paint() method of ListCore does not make this assumption. Here is the beginning of the for loop:
for (int index = first; index <= last; index++) { Object data = model.get(index); int state = getState(index); . . .
For each data item in the clip rectangle the data is obtained from the model using the value of the index parameter to locate the specific data item in the vector model. Remember that the model was previously identified as a BasicVectorContainer, a class that holds vector data. The getState() method returns the state of the data item to be repainted.
Before continuing with paint(), look at the getState() method to see how the state of the data item to be repainted is determined:
private int getState(int index) { int state = isEnabled() ? 0 : ItemPainter.DISABLED; if (selection.contains(index)) state |= ItemPainter.SELECTED; if (!isEnabled()) state |= ItemPainter.DISABLED | ItemPainter.INACTIVE; else { if (showFocus && ((focusState & ItemPainter.FOCUSED) != 0) && subfocus == index) state |= ItemPainter.FOCUSED; if ((focusState && ItemPainter.INACTIVE) != 0) state |= ItemPainter.INACTIVE; if (showRollover && rollover >= 0 && rollover == index) state |= ItemPainter.ROLLOVER; } if (!hasFocus) state |= ItemPainter.NOT_FOCUS_OWNER; return state; }
getState() returns bits that indicate the condition of the data item that is about to be repainted. Such things as the setting of the enabled property, whether the item is selected, whether the item has the subfocus, and so on determine the state bit settings.The state bits are returned as the value of state.
The paint() method concludes with three important lines of code:
ItemPainter painter = viewManager.getPainter(index, data, state); Rectangle rect = new Rectangle(r.x, r.y, r.width, itemHeight); painter.paint(data, pg, rect, state, this); } }
The first line of code obtains an item painter from the viewManager object and passes along the index of the data item, the data itself, and the display state of the data item. Remember earlier that the viewManager was set as BasicViewManager. There is no need to determine the actual painter required, as the viewManager.getPainter() does this using the parameters passed to it.
The next line of code returns the actual rectangle the painting occurs in. The last line calls the paint() method of the item painter object. The data, the graphics object on which to paint, the rectangle in which the data is painted, the display state of the data item, and the ListCore object itself is passed to paint(). (If you examine the actual source code, you'll see that the paint() method is more complex than what is presented here. The details have been omitted so you can see the key processes.)
So what you have seen is model-view architecture in action. The view obtains the data from the model and, through the view manager, passes it along to the item painter, as only the painter "knows" how to paint the item.
The result is that ListView can paint or edit anything in any way that you want it to just by putting in place the pieces you need. Painting is much quicker than if the list were managing the data itself. Except to get a count of them, ListView never has to ask the VectorModel about the data items that don't need painting at that instant. ListView can contain several million items and performance stays just as quick as if there were only three items. This is very important for any applications bound to large quantities of data.
Don't replicate your data. You shouldn't fill a BasicContainer with your data if you already have a data structure that is vector-like. Instead implement VectorModel or WritableVectorModel with your storage class. Or you can create a wrapper class that translates the VectorModel interface to access your data structure, much like JBCL does with DataSet.
When you have a VectorModel filled with your data, you must decide how you want that data displayed. If you are storing simple data such as Strings, integers, or classes that the toString() method can handle, use one or several of the item painters in the view package. If your data is more complex, such as a CORBA business object for which you want to display a chart showing the object's sales information, you must implement your own item painter.
If you don't need to switch item painters based on the address information in the model, you can use a BasicViewManager and specify your item painter. If you want to switch item painters based on the address, implement VectorViewManager and specify item painters as you like.
You can then add the item painters into ListView (or ListControl) like this:
listControl1.setModel(myVectorContainterFilledWithMyData); listControl1.setViewManager(new BasicViewManager (myTerrificItemPainter));
or
listControl1.setModel(myFabulousNewVectorModel); listControl1.setViewManager(myFabulousNewVectorViewManager);
The JBCL provides a rich set of subcomponents you can put together to build just about any kind of component you want and that can handle any data type. You choose the exact pieces you need and "plug" them into place. This flexible approach lets you create very versatile components.