Skip to main content

Tutorial: Identifying Rows

  • 9 minutes to read

This walkthrough is a transcript of the Identifying Rows video available on the DevExpress YouTube Channel.

In this tutorial, you’ll learn about the ways grid Views identify their rows.

  • Data source row indexes
  • Row handles
  • Visible indexes

Overview

Data source indexes refer to records in the bound list. You will use them for data editing. As you can expect each data row has a unique index, while group rows simply refer to the first available data row, and service rows return negative values.

GridRowLayout_DataSourceIndexes

Row handles are used by the grid View to identify rows of any type. Group rows have consecutive negative indexes, service rows have pre-defined values and data rows have positive indexes.

GridRowLayout_RowHandles

Finally, visible indexes enumerate all rows in the order they are visible on screen. These identifiers are mainly used to implement row navigation.

GridRowLayout_VisibleIndexes

Now take a closer look at when to use each type of row identifiers and how they differ from one another.

Row Identifiers in Plain Data

If you have plain data displayed by the grid, then these three identifiers are usually the same in each row. All of them are row indexes that start with 0.

GridRowLayout_InitialIndexes

Differences Between Row Identifiers when Sorting and Filtering Data

Sorting Data

Sorting data is one way to see the difference between these identifiers. The order of records has changed, and the data source indexes followed. Same rows are identified by the same data source indexes but the order is now different. On the other hand, row handles and visible indexes are still consecutive integers starting with 0 and they match each other in each row.

GridRowLayout_IndexesAfterSorting

Filtering Data

A similar effect is achieved when you filter rows. Data is reloaded, row structure is being re-built, and so visible indexes and row handles are updated to reflect the new structure, while data source indexes follow their corresponding rows.

GridRowLayout_IndexesAfterFiltering

Incorrect Using Row Handles

An important takeaway is that row handles and visible indexes change in response to user actions. Create a simple example to illustrate this point. The Save Index button in the Ribbon control will save the currently focused row’s handle. For this purpose, declare an integer savedRowHandle field and assign the grid View’s ColumnView.FocusedRowHandle property value to it.

int savedRowIndex;

private void barButtonSaveIndex_ItemClick(object sender, ItemClickEventArgs e) {
    savedRowIndex = gridView1.FocusedRowHandle;
}

There’s also the Change Value button. Its Click event handler uses the ColumnView.SetRowCellValue method to set the Name column cell to an empty string within the saved row.

private void barButtonChangeValue_ItemClick(object sender, ItemClickEventArgs e) {
    gridView1.SetRowCellValue(savedRowIndex, colName, string.Empty);
}

Run the application and first focus the row with Audi A6. Click the Save Index button, then move focus away and finally click the Change Value button. As expected, the cell in the saved row has been changed.

GridRowLayout_UsingRowHandles

Re-start the application. Now, first sort the Name column, then locate the row showing Audi A6. Save the row’s handle, which is now 2 - using the Save Index button. Then, clear sorting and notice how the row’s handle has changed. So, if you press Change Value, it will not change in the Audi A6 row that was saved.

GridRowLayout_UsingRowHandlesIncorr

Using Data Source Indexes Instead of Row Handles

To fix this, you need to modify the code so that it stores data source indexes instead of row handles. Then, in the Change Value handler, convert the stored index into a row handle and only then apply the change.

int savedRowIndex;

private void barButtonSaveIndex_ItemClick(object sender, ItemClickEventArgs e) {
    savedRowIndex = gridView1.GetDataSourceRowIndex(gridView1.FocusedRowHandle);
}

private void barButtonChangeValue_ItemClick(object sender, ItemClickEventArgs e) {
    int rowHandle = gridView1.GetRowHandle(savedRowIndex);
    gridView1.SetRowCellValue(rowHandle, colName, string.Empty);
}

Run the application to see that the code works as expected now even when using data shaping operations such as sorting or filtering.

Differences Between Row Identifiers When Grouping Data

Differences Between Row Handles and Data Source Indexes

Next see what happens when you group data. One of the key differences between row handles and data source indexes is that row handles for group rows are negative integers. There are obviously no data source indexes for group rows, because they don’t exist in the data source. So the values shown in group rows are indexes of the first data row within the group. One more thing worth noting is that row handles for data rows are always non-negative integers.

GridRowLayout_IndexesAfterGrouping

Iterating Through Rows Using Row Handles

If you want to iterate through all rows in the grid control’s memory, you can simply enumerate row handles from 0 to the View’s BaseView.DataRowCount property.

Take a look the Clear Name button’s Click event handler that does exactly this to clear values in the Name column for all currently loaded rows. The handler code is wrapped into the BaseView.BeginUpdate and BaseView.EndUpdate method calls to avoid multiple updates to the View. It starts with the row handle equal to 0 and then enumerates all integers up to the BaseView.DataRowCount property value. The loop body calls the ColumnView.SetRowCellValue method to clear the value in the Name column.

private void barButtonClearName_ItemClick(object sender, ItemClickEventArgs e) {
    gridView1.BeginUpdate();
    int rowHandle = 0;
    while (rowHandle < gridView1.DataRowCount) {
        gridView1.SetRowCellValue(rowHandle, colName, string.Empty);
        rowHandle++;
    }
    gridView1.EndUpdate();
}

Run the application. First filter the records to display only Audis. Click the button and see the names cleared. Now remove filtering and group data by Make. You’ll see the Name column has been cleared in the Audi group, but other makes still have the data.

GridRowLayout_IteratingThroughRows

So only those rows that matched the filter criteria were loaded into the memory. If you press the Clear Name button now, the change will affect all rows – in expanded or collapsed groups.

Differences Between Row Handles and Visible Indexes

The grouped View also reveals an important difference between row handles and visible indexes. First, visible indexes still start with 0 and the value is incremented with each visible row – be it a group row or a data row. Secondly, you’ll notice that row handles are already assigned to all rows loaded into the memory, including those in collapsed groups. Expand and collapse operations on group rows don’t affect row handles. Visible indexes, on the other hand, will be recalculated with each expanded state change to account for rows that have become visible or hidden.

Using Visible Indexes

To illustrate the usage of visible indexes, implement a button that navigates to the next visible row in the View – an alternative to pressing the DOWN key. The handler first determines the focused row’s visible index using the GridView.GetVisibleIndex method that takes a row handle as a parameter. Next the code increments the obtained visible index, and, finally, converts it back to a row handle value using the GridView.GetVisibleRowHandle method and sets the focus using this newly obtained handle.

private void barButtonNextRow_ItemClick(object sender, ItemClickEventArgs e) {
    int visibleIndex = gridView1.GetVisibleIndex(gridView1.FocusedRowHandle);
    visibleIndex++;
    gridView1.FocusedRowHandle = gridView1.GetVisibleRowHandle(visibleIndex);
}

Run the application. You’ll see that the button works in both plain and grouped views.

Special Row Handles

One last thing worth mentioning in this tutorial is that special types of rows, such as the New Item Row, are assigned pre-defined row handle values.

GridRowLayout_NewItemRowIndexes

To see how you can use these predefined values, handle the ColumnView.BeforeLeaveRow event. The grid control has static fields specifying them. This also includes the GridControl.InvalidRowHandle value that is returned by some methods if a row handle cannot be obtained. In the code, check whether the current row is the New Item Row, and if so, display a confirmation message box.

private void gridView1_BeforeLeaveRow(object sender, DevExpress.XtraGrid.Views.Base.RowAllowEventArgs e) {
    if (e.RowHandle == DevExpress.XtraGrid.GridControl.NewItemRowHandle) {
        DialogResult result = MessageBox.Show("Are you done editing the new record?", "Confirmation", MessageBoxButtons.YesNo);
        e.Allow = (result == System.Windows.Forms.DialogResult.Yes);
    }
}

Run the application. Focus the New Item Row and then try to change focus back to one of the data rows. If you click No, the focus will stay unchanged.

GridRowLayout_BeforeLeaveFocusResult

Converting Row Identifiers into One Another

Grid Views provide methods allowing you to convert row identifiers into one another. To see how this works, analyze the handler that displays row index information in this application.

There are three columns, one displays visible indexes, another row handles and the third - data source indexes. The code first obtains the data source index using the row handle passed as a parameter. Then, the visible index is determined using the row handle. After that all the values are displayed in their corresponding columns.

using DevExpress.XtraGrid.Views.Grid;
//...
private void GridView1_CustomDrawCell(object sender, DevExpress.XtraGrid.Views.Base.RowCellCustomDrawEventArgs e) {
    GridView view = sender as GridView;
    int dataSourceRowIndex = view.GetDataSourceRowIndex(e.RowHandle);
    int visibleIndex = view.GetVisibleIndex(e.RowHandle);
    if(e.Column.FieldName == "gridColumnRowHandle")
        e.DisplayText = e.RowHandle.ToString();
    if(e.Column.FieldName == "gridColumnVisibleIndex")
        e.DisplayText = visibleIndex.ToString();
    if(e.Column.FieldName == "gridColumnListSourceIndex")
        e.DisplayText = dataSourceRowIndex.ToString();
}

Key Points

Now go over the key points once again.

Data source indexes

  • Specify zero-based row indexes in the bound list.
  • Follow rows when you sort, group or filter data.
  • For group rows, they will point to the first data row in the group.
  • Used for accessing data.

Row Handles

  • Data row handles are zero-based indexes that correspond to row order from top to bottom.
  • Group row handles are negative values that start with -1. The order matches the order of group rows from top to bottom.
  • The grid specifies reserved row handles for the New Item Row, Auto Filter row and an Invalid Row Handle.
  • Row handles are re-assigned to rows after each data operation.
  • When the View is filtered, rows and row handles are created only for rows that match the filter.

Visible Indexes

  • Zero-based indexes that match the order of visible rows, from top to bottom.
  • Service rows get negative indexes if displayed above data and group rows.
  • Re-assigned after each data operation, including data sorting, grouping and filtering.
  • Visible indexes are only assigned to rows in expanded groups. Thus, the indexes are updated after each expand/collapse operation.
See Also