Writing a composite component

To help you understand how a model-view component works, this chapter examines the ListControl component that is available on the JBuilder Component Palette. ListControl is a complex component, presenting its data in a list the user can scroll. It is built using other classes that implement several interfaces.

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:


Examining the structure of ListControl

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.


Implementing interfaces

ListView and ListCore implement several interfaces. Together they provide the functionality that makes a list control a view for vector data and a listener for specific events.

Interfaces implemented in ListView

ListView implements the VectorView and VectorSubfocusListener interfaces.

By implementing VectorView, ListView provides several properties to the control, but delegates their behavior to ListCore. For example, this is the definition of the accessor methods for the readOnly property; note that methods of the same name in ListCore are called within the implementation:

public boolean isReadOnly() {
    return core.isReadOnly();
}

public void setReadOnly(boolean ro) {
    core.setReadOnly(ro);
}
The methods that implement VectorSubfocusListener are called when the focus of an item within the list control changes to another item.

Interfaces implemented in ListCore

ListCore implements the KeyListener, FocusListener, VectorModelListener, VectorSelectionListener, and VectorView interfaces. Therefore, ListCore listens for key, focus, vector-model, and vector-selection events and responds to them when they occur.

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;
}

Setting the model, item painter, and item editor

Because ListCore listens for vector model events, it appears that the list control uses a vector model. To have an object that can hold vector data, the list control needs to instantiate an object when ListControl is created. Here is the constructor of ListControl and an excerpt from the method the constructor calls:
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 SelectableTextItemPainter 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 SelectableTextItemPainter(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.


Model-view architecture in action

To see how model-view architecture works within the list control, examine the code that handles the repainting of the control when it becomes visible after it is partially hidden. By following it all the way through, you can see how the list control requests the data from the BasicVectorModel and then passes it to the BasicViewManager, which selects the SelectableTextItemPainter to repaint the items in the list control that need repainting.

Uncovering a partially obscured list control

In any windowing system, it is common for users to move windows around on the screen. When this happens, some windows can become temporarily obscured. When an obscured window becomes visible again, it must be repainted. In this case, the window is the list control. The AWT calls the paint() method of ListCore when the list control becomes entirely visible.

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() returns the state of the data item to be repainted. The state can be some combination of enabled or disabled, selected or not selected, focused or not focused, active or inactive.

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 (focusState == ItemPainter.INACTIVE)
        state |= ItemPainter.INACTIVE;
    if (showFocus && focusState == ItemPainter.FOCUSED && subfocus == index)
        state |= ItemPainter.FOCUSED;
    return state;
}
getState() returns bits that indicate the condition of the data item that is about to be repainted. If the enabled property of the item is false, the DISABLED bit is set. If the contains property of the vector selection pool returns true, meaning the data item is selected, the SELECTED bit is added. If data the item does not have the subfocus, the INACTIVE bit is set. Finally, if the item does have the subfocus and the showFocus property is true, the FOCUSED bit is added. 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().

This 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.