Creating a data-aware component

A data-aware component is a component that can be linked with a data set. It can display and edit the data in one or more of the data set's columns.

All of the controls on the dbSwing page of the Component Palette are data-aware. Many of the controls on the JBCL page are also data-aware. For example, the ChoiceControl, ListControl, and TextFieldControl components can display and edit data in a column of a data set, while the GridControl can display and edit the data in all rows and columns of a data set.

You can also use the JBCL controls with data that doesn't come from a data set. For example, your application can supply a list of items to display in a ListControl. Because the data isn't obtained from a data set, the list control is not being used as a data-aware component.

This chapter describes how to make a component data aware. These are the topics covered:


Requirements for a data-aware control

JBCL data-aware components are model-view components. They specify a model, a container for the data. They also specify an item painter to display the data and they specify an item editor to edit the data. For information about setting models, item painters, and item editors, see Writing a composite component.

Every data-aware control must have a way to identify the data set it links with, and the column it accesses. So each data-aware control needs a dataSet property and most need a columnName property. Grids are examples of controls that won't need columnName, as they display all columns in a data set. Data-aware status bars also don't need columnName; they don't display the data, but report on the status of data operations.

For controls that need only a dataSet property, implement the DataSetAware interface, which contains just the getter and setter methods for a single dataSet property. For controls that need both a dataSet and a columnName property, implement the ColumnAware interface. ColumnAware contains just the getter and setter methods for a columnName property, but it extends the DataSetAware interface, so your data-aware control must implement the dataSet property, too.

Data-aware components can implement four additional data-access interfaces: AccessListener, DataChangeListener, NavigationListener, and StatusListener. Each interface has just one method to implement. See the following table:

Data-access interfaces

Interface Method Purpose

AccessListener accessChange() Responds to AccessEvent occurrences, such as opening, posting to, and closing a data set.
DataChangeListener dataChanged() Responds to DataChangeEvent occurrences, such as a change in a data value or the data being sorted.
NavigationListener navigated() Responds to NavigationEvent occurrences, such as the user moving to a new row in the control.
StatusListener statusMessage() Responds to StatusEvent occurrences, such as loading the data, throwing exceptions, and so on. It is used primarily by data-aware status display controls, such as JBCL's StatusBar.

A data-aware control needs a way to access a data set and obtain the data to display. If a control is not read-only, it also needs a way to edit that data. You must write a few methods to accomplish these tasks. For example, the ListControl component has an openDataSet() method that opens the data set and calls another method, bindDataSet(), which fetches the data and ultimately displays it in the list control. ListControl also has a setItems() method, an accessor method for the items property, which holds the list of strings in the list control.

Summary of data-aware control requirements

Most data-aware controls need


ListControl: a data-aware example

The ListControl component found on the Controls tab of the Component Palette is a data-aware control. The Writing a Composite Component chapter introduced you to the some of the inner workings of ListControl. This section again uses ListControl and shows you what makes it a data-aware control. Find the source code for ListControl in the controls package and refer to it as you read through the remainder of this chapter.

Adding data-access interfaces

ListControl is defined as extending the ListView class and as implementing several interfaces, including the three data-access interfaces: AccessListener, DataChangeListener, and NavigationListener. Here is the beginning of the ListControl declaration:
public class ListControl
     extends ListView
  implements NavigationListener, DataChangeListener, AccessListener,
             VectorSubfocusListener, WritableVectorModel, BlackBox, ColumnAware
{ 
    . . .
Note that ListControl implements the ColumnAware interface, so ListControl must have a dataSet and a columnName property.

Adding a dataSet property

Like all data-aware controls, ListControl has a dataSet property:
  public DataSet getDataSet() { return dataSet; }

  public void setDataSet(DataSet newDataSet) {
  if (dataSet != null) {
    dataSet.removeAccessListener(this);
    dataSet.removeNavigationListener(this);
    dataSet.removeDataChangeListener(this);
  }
  openDataSet(newDataSet);
  if (dataSet != null) {
    dataSet.addAccessListener(this);
    dataSet.addNavigationListener(this);
    dataSet.addDataChangeListener(this);
  }
}
The setDataSet() method opens the specified data set and the control registers itself as a listener for access, navigation, and data change events in the data set.

Adding a columnName property

ListControl displays data from a column of the specified data set. Which column it displays depends on the setting of the columnName property:
private String columnName;

public String getColumnName() { return columnName; }

public void setColumnName(String newColumnName) {
columnName = newColumnName;
if (addNotifyCalled)
    openDataSet(dataSet);
}

setColumnName() specifies the column to use in the data set and calls the openDataSet() method, passing to it the value of the dataSet property.

Fetching the data

The openDataSet() method makes the data set passed to it the current dataSet property value. If a data set is specified an attempt is made to open the data set. If the data set is now open, the control binds with the data in the data set.

Here is the code for openDataSet() in its entirety:

private void openDataSet(DataSet newDataSet) {
dataSet = newDataSet;
if (dataSet == null) {
  buildStringList(null);
  return;
}
else if (addNotifyCalled && !dataSet.isOpen()) {
  try {
    dataSet.open();
  }
  catch (DataSetException ex) {
    DataSetException.handleException(dataSet, this, ex);
    return;
  }
}
if (dataSet.isOpen()) {
  bindDataSet();
}
}
As you can see, once the dataSet property has a new value, openDataSet() checks to see if the dataSet value is null. If it isn't and the ListControl is initialized, openDataSet() attempts to call the open() method of the data set. Finally openDataSet() calls the bindDataSet() method.

So far the data set is open and the ListControl is registered as a listener of critical, data-access events, but data from the data set has not yet been retrieved. This is the task of bindDataSet().

In Understanding model-view component architecture, you learned how a model-view component fetches the data from the model and passes it to a view manager, which selects a painter to paint the item in the view. In the Writing a composite component chapter, you saw how ListControl set the model property value using the BasicVectorContainer class. You also saw that ListControl specified the BasicViewManager as the view manager. These classes are sufficient for a control that is not data aware.

The bindDataSet() method specifies an instance of the VectorDataSetManager class as the model and view manager. VectorDataSetManager is a data-aware class that allows vector JavaBeans to access the data set. Here is the beginning of the bindDataSet() method:


private boolean bindDataSet() {
  Column column;
  if (dataSet != null && (column = dataSet.hasColumn(columnName)) != null) {
    setBatchMode(true);
    VectorDataSetManager cursorManager = new VectorDataSetManager(dataSet, column, this);
    super.setModel(cursorManager);
    super.setViewManager(cursorManager);
    navigateDataSet = true;
         . . .
The last line shown sets a navigateDataSet variable to true. It is used by the navigateWithDataSet property. This is how navigateWithDataSet is defined:
public boolean isNavigateWithDataset() {
    return userSetNavigate;
 }

public void setNavigateWithDataSet(boolean navigate) {
    userSetNavigate = navigate;
    if (userSetNavigate && navigateDataSet && dataSet != null && dataSet.isOpen())
        setSubfocus(dataSet.row());
}
bindDataSet() sets navigateDataSet to true after the model and view manager are set. When navigateWithDataSet is set to true and a data set has been specified and the specified data set is open, setSubfocus() is called. setSubfocus() makes the current row of the data set column the subfocus item of the ListControl .

The next lines of code in bindDataSet() look like this:

    resetSelection();
    bindProperties(column);
    if (topIndex > -1) {
        super.setTopIndex(topIndex);
        topIndex = -1;
    }
    if (isShowing() && !isBatchMode())
        doLayout();
    ...   
resetSelection() makes the new subfocus item the selected item in the list control, or, if the multiSelect property is true, adds all selected items to the selection pool. It also repaints the control. Here is the resetSelection() method:
private void resetSelection() {
    if (multiSelect) {
        int[] selections = getSelection().getAll();
        setSelection(new BasicVectorSelection());
        getSelection().add(selections);
    }
    else {
        setSelection(new SingleVectorSelection());
        getSelection().add(getSubfocus());
    }
    repaint(50);      // repaints in 50 milliseconds
}
Writing a composite component describes what happens when ListControl repaints. Briefly, the data is fetched from the model, passed to an item painter managed by the view manager, and the item painter paints the control. When ListControl displays the data in a data set, however, a different model and view manager are used. When ListControl is being used as a data-aware control, an instance of the VectorDataSetManager class gets the data from the model and specifies the item painter to use for painting the data in the list control.

The bindProperties() method gives the column displayed in the list control the same color, font, and alignment attributes as the column it accesses in the data set.

Implementing the AccessListener interface

The AccessListener interface has one method to implement: accessChange(). This is the implementation of accessChange() in ListControl:
public void accessChange(AccessEvent event) {
    switch(event.getID()) {
        case AccessEvent.OPEN:
            try {
                openDataSet(dataSet);
            }
            catch (Exception ex) {
                event.appendException(ex);
            }
            break;
    case AccessEvent.CLOSE:
        safeEndEdit(false);
        buildStringList(null);
        break;
    default:
        break;
    }
}
accessChange() is called whenever an access event occurs. An access event occurs whenever an attempt is made to open, close, or change the data set. ListControl's accessChange() method processes open and close access events. If an open access event occurs, the method tries to open the data set. If a close access event occurs, the accessChange() ends any edit session and fills the control with null values.

Implementing the DataChangeListener interface

The DataChangeListener interface has one method to implement: dataChanged(). This is the implementation of dataChanged() in ListControl:
public void dataChanged(DataChangeEvent e) {}
dataChanged() is called whenever a data change event occurs. A data change event occurs whenever one or more rows in the data set are added, changed, or deleted. ListControl's dataChanged() method does nothing.

Implementing the NavigationListener interface

The NavigationListener interface has one method to implement: navigated(). This is the implementation of navigated() in ListControl:
public void navigated(NavigationEvent e) {
    if (!dsNavigating && userSetNavigate && navigateDataSet && getSubfocus() != dataSet.row())
        setSubfocus(dataSet.row());
}
navigated() is called whenever a navigation event occurs. A navigation event occurs when an attempt to change the subfocus item in ListControl is made. For example, if the user presses the down-arrow key to move to the next item in the list control and, therefore, the next row in the data set, a navigation event occurs.

navigated() sets the subfocus on the column specified as the value of columnName in the current row of the data set.

When setSubfocus() is called, a subfocus event occurs. ListControl overrides the subfocusChanging() method, which is called in response to a changing of the focus. Here is the subfocusChanging() method of ListControl:

public void subfocusChanging(VectorSubfocusEvent e) throws VetoException ) {
    if (dataset != null && dataSet.isOpen() && navigateDataSet) {
        if (dataSet.row() != e.getLocation()) {
            try {
                dsNavigation = true;
                if (!dataSet.goToRow(e.getLocation())) {
                    dsNavigating = false;
                    throw new VetoException();
                }
            catch (DataSetException ex) {
                DataSetException.handleException(dataSet, this, ex);
                dsNavigating = false;
                throw new VetoException();
            }
        }
       dsNavigating = false;
    }
}