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:
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. |
|
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.
public class ListControl extends ListView implements NavigationListener, DataChangeListener, AccessListener, WritableVectorModel, BlackBox { . . .
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.
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.
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.
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.
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.
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; } }