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 com.borland.jbcl.control.ListControl
.
ListControl
component you see on JBuilder's
component palette extends the ListView
component. ListControl
has an items
property that makes it very easy to
populate the list with Strings
at design time. It has a multiSelect
property that allows you to change the list so that the user
can select more than one item at a time. It has dataSet
and
columnName
properties that you use to make the control
data-aware. These properties make the control easy to use in the
UI designer.
ListControl
is a view of a vector of data, a
one-dimensional data structure. In JBCL, a one-dimensional data
structure is a VectorModel
, an interface that gives you
everthing you need to read data from a one-dimensional data
structure. All the data passed to the methods of the VectorModel
interface are of type Object
. Therefore, you can
access any data of any type through VectorModel
.
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.
VectorModel
stores the data. To see that data,
you need the ListView
class, which knows how to display
the contents of a VectorModel
.
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.
ListView
has no knowledge of the data types
stored in the VectorModel
, it passes the task of
painting the items to the ItemPainter
interface. ItemPainter
has a paint()
method with five arguments:
Graphics
object to paint into ItemPaintSite
interface that is used to
obtain the foreground, background, font, and such
properties of the site where the item is painted 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.
ViewManager
interface selects the appropriate
item painter to paint the item at the appropriate location. All
view managers have a getPainter()
method that takes
three arguments:
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.
ItemEditor
interface, which contains everything
needed to control an edit session, the period of time spent
editing the data item.
Item editors have a startEdit()
method that has these
three arguments:
ItemEditSite
interface that is used to obtain
information about the host container and the details of a
particular edit sessionWhen 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:
getValue()
- returns the data value being
edited. getComponent()
- returns the component added to
the host container as an editor. changeBounds()
- called when the editor site
changes size. canPost()
- determines whether the value in the
editor is valid for posting. endEdit()
- called just before the editor is
removed and is used for an necessary cleanup work. For more information about the item editors in JBCL, see Item painters and item editors.
ListControl
appears
uncomplicated and easy to use. It is the components that make up ListControl
that contain its underlying functionality. To understand how
ListControl
does what it does, you must examine ListView
,
which is in turn is composed of other components.
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
.
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.
ListView
implements the VectorView
interface. 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); }
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; }
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 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.
BasicVectorModel
and
then passes it to the BasicViewManager
, which selects
the SelectableItemPainter
to repaint the items in the
list control that need repainting.
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()
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.
ListView
to show. What data types
are you going to store in your VectorModel
? Will BasicVectorContainer
meet your needs or do you already have a vector-like data
structure that you want to display in ListView
?
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.