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.

Many of the controls on the Controls tab of the Component Palette are 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 these same 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. The same component can be used as a data-aware component or as a component that is not data aware.

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 or columns 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.

Data-aware components can implement four 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.

Because most data-aware controls can also be used as controls that are not data aware, the control should have a means to determine if an item displayed in the control comes from a data set when the subfocus changes. The JavaBeans data-aware controls on the Controls tab of the Component Palette use a boolean property called navigateWithDataSet for this purpose.

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 that 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, 
        WritableVectorModel, BlackBox
{ 
    . . .

Adding a dataSet property

Like all data-aware controls, ListControl has a dataSet property:
private dataSet;

public DataSet getDataSet() {
    return dataSet;
}

public void setDataSet(DataSet newDataSet) {
    setNavigateWithDataSet(true);
    openDataSet(newDataSet);
}
The setDataSet() method sets the navigateWithDataSet property to true and opens the specified data set. We'll examine the navigateWithDataSet property later.

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) {
    column = newColumnName;
    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 primary tasks of the openDataSet() method are to open the specified data set and fill the list control with the data items retrieved from the specified column of the data set. openDataSet() first removes all listeners for navigation, data change, and access events of any previous data set. Then it makes the data set passed to it the current dataSet property value. Here is the code for openDataSet() in its entirety:
private void openDataSet(Dataset newDataset) {
    if (dataSet != null) {
        dataSet.removeNavigationListener(this);
        dataSet.removeDataChangeListener(this);
        dataSet.addDataChangeListener(this);
    }
    dataSet = newDataSet;
    if (dataSet == null)
        buildStringList(null);
    else if (initialized) {
        try { dataSet.open(); }
        catch (DataSetException ex) {
            DataSetException.handleException(dataSet, this, ex);
            return;
        }
        dataSet.addAccessListener(this);
        dataSet.addNavigationListener(this);
        dataSet.addDataChangeListener(this);
        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. If the open is successful, openDataSet() calls the addAccessListener(), addNavigationListener(), and addDataChangeListener() methods of the data set, thereby registering ListControl as a listener of the data set's access, navigation, and data change events. 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) {
        VectorDataSetManager cusorManager = new VectorDataSetManager(dataSet,
            column, this, getAlignment(), getItemMargins());
        super.setModel(cursorManager);
        super.setViewManager(cursorManager);
        setNavigateWithDataSet(true);
        . . .
The last line shown sets the navigateWithDataSet property to true. This is how navigateWithDataSet is defined:
private boolean navigate;

public boolean isNavigateWithDataset() {
    if (getModel() instanceof VectorDataSetManager)
        return navigateDataSet;
    else
        return false;
}

public void setNavigateWithDataSet(boolean navigate) {
    navigateDataSet = navigate;
    if (navigateDataSet && dataSet != null && dataSet.isOpen())
        setSubfocus(dataSet.row());
}
You have already seen in Adding a dataSet property that specifying a data set for ListControl sets the navigateDataSet property to true. bindDataSet() also sets navigateDataSet to true after the model and view manager are set. When navigateDataSet is set to true and a data set has been specified and the specified data set is open, setSubfocus() is called. setSubfocus() makes the subfocus item of the ListControl the current row in the data set.

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

    resetSelection();
    if (isShowing())
        doLayout();
    return true;
    . . .
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.

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:
        endEdit(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) {
    if (e.getID() == e.DATA_CHANGED)
        repaint(50);      // repaint in 50 milliseconds
}
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 simply repaints the list control.

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 (!cursorNavigating && 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 {
                cursorNavigation = true;
                if (!dataSet.goToRow(e.getLocation())) {
                    cursorNavigating = false;
                    throw new VetoException();
                }
            catch (DataSetException ex) {
                DataSetException.handleException(dataSet, this, ex);
                cursorNavigating = false;
                throw new VetoException();
            }
        }
        cursorNavigating = false;
    }
}