Tutorial: Group Row API
- 7 minutes to read
This walkthrough is a transcript of the Group Row API video available on the DevExpress YouTube Channel.
The tutorial will customize grid behavior using the group row API. You will create a sample application where:
- the grid automatically expands focused group rows on the root level;
- end-users cannot collapse those rows;
- other root-level group rows are collapsed automatically;
- parent or child row data is automatically displayed in the status bar.
Starting Point
To learn the basics of identifying and traversing group rows, begin with an application that has a GridControl with grouping already applied.
Displaying the Focused Row Handle
First, display the focused row handle in the status bar. Select the grid View and handle its ColumnView.FocusedRowChanged event, which is raised when focus moves between rows. The currently focused row is specified by the event’s FocusedRowChangedEventArgs.FocusedRowHandle parameter. A separate method is used to display the information in the status bar.
private void DisplayRowHandle(int rowHandle) {
// Display the focused row handle.
siRowHandleInfo.Caption = "Focused Row Handle: " + rowHandle.ToString();
}
private void gridView_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e) {
int focusedRowHandle = e.FocusedRowHandle;
DisplayRowHandle(focusedRowHandle);
}
Run the application. Since the top group row is now focused, the status bar displays its handle that is equal to -1. Focus the following group row and see that its handle is -2. As you see, group row handles are negative integer numbers starting with -1. Note that rows are numbered from top to bottom, regardless of their nesting level.
Data row handles are non-negative integer numbers starting with 0. The first data row’s handle is 0, then 1, and so on.
Expanding the Focused Root Group Row
Now modify the grid’s functionality so that root-level group rows are automatically expanded when they are focused. Add one more method call to the ColumnView.FocusedRowChanged event handler. In that method, check whether the focused row is a root group row using the GridView.GetRowLevel method. For group rows displayed at the top hierarchy level, the method returns 0. After that, expand the focused group row by calling the GridView.SetRowExpanded method. Pass the following parameters to this method: the focused row handle, true to expand the group row and true to additionally expand child group rows at all nesting levels.
private void ExpandFirstLevelGroupRow(int rowHandle) {
// Check whether the specified row is a first-level group row.
if (gridView.GetRowLevel(rowHandle) == 0)
// Expand the row.
gridView.SetRowExpanded(rowHandle, true, true);
}
private void gridView_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e) {
// ...
ExpandFirstLevelGroupRow(focusedRowHandle);
}
Run the application. The focused root group row is automatically expanded along with its nested groups.
Collapsing Non-Focused Root Group Rows
Now modify the behavior so that only one root-level group row is expanded at a time. Add one more method call to the event handler – CollapseFirstLevelGroupRows. The method iterates all group rows by counting down from -1 and until the counter reaches an invalid row handle. Non-focused root group rows are collapsed along with their nesting group rows using the GridView.SetRowExpanded method with false passed as its second parameter.
private void CollapseFirstLevelGroupRows(int rowHandleToKeepExpanded) {
// Check whether the specified row is a root group row.
if (gridView.GetRowLevel(rowHandleToKeepExpanded) == 0) {
// Collapse all root group rows except the specified row.
int rowHandle = -1;
while (gridView.IsValidRowHandle(rowHandle)) {
if ((rowHandle != rowHandleToKeepExpanded) && (gridView.GetRowLevel(rowHandle) == 0))
gridView.SetRowExpanded(rowHandle, false, true);
rowHandle--;
}
}
}
private void gridView_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e) {
// ...
CollapseFirstLevelGroupRows(focusedRowHandle);
}
Now run the application again. When a root group row receives focus, this group is automatically expanded along with its nested groups, while other groups are collapsed. So, a single root group can be expanded at any time. Note that end-users can collapse the focused group row. There’s a way to change this behavior too.
Preventing End-Users from Collapsing the Expanded Root Group Row
Close the application. Declare a variable that will store the handle of the root group row that was expanded last. Handle the View’s GridView.GroupRowExpanded event, which is fired after a group row has been expanded. Save the currently expanded root group row using the previously declared variable. After that, handle the View’s GridView.GroupRowCollapsing event, which is raised when end-users attempt to collapse a group row. To prevent the currently expanded root group row from being collapsed, set the event’s RowAllowEventArgs.Allow parameter to false.
using DevExpress.XtraGrid.Views.Grid;
//...
int expandedRowHandle;
private void gridView_GroupRowExpanded(object sender, DevExpress.XtraGrid.Views.Base.RowEventArgs e) {
GridView view = sender as GridView;
if (view == null) return;
// Save the currently expanded root group row.
if (view.GetRowLevel(e.RowHandle) == 0)
expandedRowHandle = e.RowHandle;
}
private void gridView_GroupRowCollapsing(object sender, DevExpress.XtraGrid.Views.Base.RowAllowEventArgs e) {
// Prevent the expanded root group row from being collapsed.
if (e.RowHandle == expandedRowHandle)
e.Allow = false;
}
Run the application to see the result. Now you cannot collapse the focused root group row. So, one root group row is always expanded. Its nested group rows can have any status.
Displaying Group Information
Now return to design time and further modify the ColumnView.FocusedRowChanged event handler. Display information on the current group when a group row or data row is focused.
Create the DisplayParentChildInfo function and call it in the event handler. To determine whether the focused row handle refers to a group row, use the GridView.IsGroupRow method. The GridView.GetChildRowCount function returns the number of children for a specific group row.
For a focused data row, the status bar should display information on its parent group rows at all levels. Go up the group row hierarchy using the GridView.GetParentRowHandle method. At every hierarchical level, read the text displayed within a group row using the GridView.GetGroupRowDisplayText method.
private void DisplayParentChildInfo(int rowHandle) {
// For a group row, display the number of its children.
if (gridView.IsGroupRow(rowHandle))
siParentChildInfo.Caption = "Group child count: " + gridView.GetChildRowCount(rowHandle).ToString();
else {
// For a data row, display info on parent group rows.
int parentRowHandle = gridView.GetParentRowHandle(rowHandle);
string caption = "Group row ";
while (gridView.IsValidRowHandle(parentRowHandle)) {
caption += "- " + gridView.GetGroupRowDisplayText(parentRowHandle);
parentRowHandle = gridView.GetParentRowHandle(parentRowHandle);
}
siParentChildInfo.Caption = caption;
}
}
private void gridView_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e) {
// ...
DisplayParentChildInfo(focusedRowHandle);
}
Run the application and see the result. When you focus a group row, the status bar displays the number of its children. If the focused row is a data row, the status bar displays information about parent group rows at all levels.
Complete Code
using DevExpress.XtraGrid.Views.Grid;
private void DisplayRowHandle(int rowHandle) {
// Display the focused row handle.
siRowHandleInfo.Caption = "Focused Row Handle: " + rowHandle.ToString();
}
private void ExpandFirstLevelGroupRow(int rowHandle) {
// Check whether the specified row is a first-level group row.
if (gridView.GetRowLevel(rowHandle) == 0)
// Expand the row.
gridView.SetRowExpanded(rowHandle, true, true);
}
private void CollapseFirstLevelGroupRows(int rowHandleToKeepExpanded) {
// Check whether the specified row is a root group row.
if (gridView.GetRowLevel(rowHandleToKeepExpanded) == 0) {
// Collapse all root group rows except the specified row.
int rowHandle = -1;
while (gridView.IsValidRowHandle(rowHandle)) {
if ((rowHandle != rowHandleToKeepExpanded) && (gridView.GetRowLevel(rowHandle) == 0))
gridView.SetRowExpanded(rowHandle, false, true);
rowHandle--;
}
}
}
private void DisplayParentChildInfo(int rowHandle) {
// For a group row, display the number of its children.
if (gridView.IsGroupRow(rowHandle))
siParentChildInfo.Caption = "Group child count: " + gridView.GetChildRowCount(rowHandle).ToString();
else {
// For a data row, display info on parent group rows.
int parentRowHandle = gridView.GetParentRowHandle(rowHandle);
string caption = "Group row ";
while (gridView.IsValidRowHandle(parentRowHandle)) {
caption += "- " + gridView.GetGroupRowDisplayText(parentRowHandle);
parentRowHandle = gridView.GetParentRowHandle(parentRowHandle);
}
siParentChildInfo.Caption = caption;
}
}
private void gridView_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e) {
int focusedRowHandle = e.FocusedRowHandle;
DisplayRowHandle(focusedRowHandle);
ExpandFirstLevelGroupRow(focusedRowHandle);
CollapseFirstLevelGroupRows(focusedRowHandle);
DisplayParentChildInfo(focusedRowHandle);
}
int expandedRowHandle;
private void gridView_GroupRowExpanded(object sender, DevExpress.XtraGrid.Views.Base.RowEventArgs e) {
GridView view = sender as GridView;
if (view == null) return;
// Save the currently expanded root group row.
if (view.GetRowLevel(e.RowHandle) == 0)
expandedRowHandle = e.RowHandle;
}
private void gridView_GroupRowCollapsing(object sender, DevExpress.XtraGrid.Views.Base.RowAllowEventArgs e) {
// Prevent the expanded root group row from being collapsed.
if (e.RowHandle == expandedRowHandle)
e.Allow = false;
}