The code in Listing 1
creates a form with three DataGrid controls each displaying one of three inter-related tables: employees, orders, and order details. When the user selects an employee, the form updates the order and detail view to show all orders processed by the employee, and all the items included in the first order in the view. The user manually selects the item in the first DataGrid and the other two DataGrids update themselves automatically based on the services of the binding manager.
Let's focus on the following excerpt from Listing 1
EmployeeGrid.DataSource = m_data
EmployeeGrid.DataMember = "Emp"
OrdersGrid.DataSource = m_data
OrdersGrid.DataMember = "Emp.Emp2Ord"
DetailsGrid.DataSource = m_data
DetailsGrid.DataMember = "Emp.Emp2Ord.Ord2Det"
All three DataGrids are bound to the same data source objecta DataSet object named m_data
in the example. The DataMember
property of a data-bound control selects a collection of data out of DataSet. In most cases, DataMember
points to the name of a child DataTable object. The EmployeeGrid control, for example, displays the contents of the Emp
table. A bit more interesting is the expression assigned to the DataMember
property of the other two DataGrid controls. The expression is based on a custom syntaxa dot-separated sequence of stringsthat data-bound controls can interpret. The value of the DataMember
property is known as a navigation path
within the data source.
A navigation path resolves to a list or property in the data source. The expression typically includes the table name plus column name or one or more relation names
. A relation name is the name of any DataRelation object defined between a pair of DataTables in the DataSet. Let's consider the expression Emp.Emp2Ord
, which is the name of a data relation object defined as follows.
Dim col1 As DataColumn
Dim col2 As DataColumn
col1 = m_data.Tables("Emp").Columns("employeeid")
col2 = m_data.Tables("Ord").Columns("employeeid")
Dim rel1 As DataRelation
rel1 = New DataRelation("Emp2Ord", col1, col2)
The data relation defines a link between the employees and the orders table set on the common employeeid
column. When assigned to the DataMember
property of a grid, the expression Emp.Emp2Ord
indicates that the grid will display all records from the orders table related to the currently selected record on the parent table of the relationthe employees table.
A similar pattern determines the meaning of the expression Emp.Emp2Ord.Ord2Det
. In this case, displayed records come from the table with order details and consist of all items in the currently selected order.
When a DataGrid control is bound to a data source, it retrieves the current binding manager for the data source and hooks up the CurrentChanged
event. When the current record on the data source changes (no matter which control modified the selection), the DataGrid receives an event. At this point, each DataGrid control by default looks at the navigation path stored in its DataMember
property. The first token, if any, is considered as the table name. The next token can either be a column or relation name. If it is a relation name then the grid retrieves all the records in the child table that match the relation. This is accomplished using the GetChildRows
method on the DataRow class.
DataRow parentRow = ... ;
childRows = parentRow.GetChildRows(relName);
|Figure 2: A master/detail page with three grids.|
As you can see, GetChildRows returns an array of DataRow objects. The elements in the array are then used to populate the grid. In this way, each DataGrid with a proper navigation path can automatically refresh its contents whenever a selection changes in the logical tree of data. As a margin note, consider that no DataGrid control you may have in the form is aware of the others. Figure 2
shows the form of Listing 1
in action in a sample application.
Any click on a grid to select a record pushes changes to other grids down the hierarchy. Which grid is the master, and which is the slave. is not written anywhere; or, at least, it is not written clearly or in code. The grid's role is wholly determined by the navigation path assigned to the DataMember
property of the control. The data to display is extracted from the same data source objectthe DataSet class.