Back to Devexpress

Working with Master-Detail Relationships in Code

windowsforms-732-controls-and-libraries-data-grid-master-detail-working-with-master-detail-relationships-in-code.md

latest51.2 KB
Original Source

Working with Master-Detail Relationships in Code

  • May 30, 2025
  • 23 minutes to read

Bind to a Database with Master-Detail

The following example binds the DevExpress WinForms Data Grid to the nwind.mdb database and display a master-detail relationship. This database ships as part of DevExpress WinForms Demos and is located at C:\Users\Public\Documents\DevExpress Demos 2x.x\Components\Data.

csharp
using System.Data;
using System.Data.OleDb;
using DevExpress.Utils;
using DevExpress.XtraEditors.Repository;
using DevExpress.XtraGrid.Views.Card;

namespace DXDataGridMasterDetailApp {
    public partial class Form1 : DevExpress.XtraEditors.XtraForm {
        public Form1() {
            InitializeComponent();
            // Creates a connection to the Nwind database.
            OleDbConnection connection = new OleDbConnection(
                "Provider=Microsoft.Jet.OLEDB.4.0;Data Source = C:\\Users\\Public\\Documents\\DevExpress Demos 25.2\\Components\\Data\\nwind.mdb");
            // Creates the data adapters to retrieve data from the Categories and Products data tables.
            OleDbDataAdapter adapterCategories = new OleDbDataAdapter(
                "SELECT CategoryID, CategoryName, Picture FROM Categories", connection);
            OleDbDataAdapter adapterProducts = new OleDbDataAdapter(
                "SELECT CategoryID, ProductID, ProductName, UnitPrice FROM Products", connection);

            DataSet dataSetNwind = new DataSet();
            // Creates DataTable objects that correspond to database tables.
            adapterCategories.Fill(dataSetNwind, "Categories");
            adapterProducts.Fill(dataSetNwind, "Products");

            // Sets up a master-detail relationship between data tables.
            DataColumn keyColumn = dataSetNwind.Tables["Categories"].Columns["CategoryID"];
            DataColumn foreignKeyColumn = dataSetNwind.Tables["Products"].Columns["CategoryID"];
            dataSetNwind.Relations.Add("CategoriesProducts", keyColumn, foreignKeyColumn);

            // Binds the Data Grid to a data source.
            gridControl1.DataSource = dataSetNwind.Tables["Categories"];
            // Forces the Data Grid to initialize its settings.
            gridControl1.ForceInitialize();

            // Creates a pattern view (CardView) to display detail data.
            CardView cardViewProducts = new CardView(gridControl1);
            gridControl1.LevelTree.Nodes.Add("CategoriesProducts", cardViewProducts);
            // Specifies the detail view's caption (the caption of detail tabs).
            cardViewProducts.ViewCaption = "Category Products";

            // Hides the CategoryID column from the master view.
            gridView1.Columns["CategoryID"].VisibleIndex = -1;

            /* Creates a Picture Edit repository item to display images in the Picture column,
            * adds it to the Data Grid's RepositoryItems collection, and sets up image settings.
            */
            RepositoryItemPictureEdit riPictureEdit = gridControl1.RepositoryItems.Add("PictureEdit") as RepositoryItemPictureEdit;
            gridView1.Columns["Picture"].ColumnEdit = riPictureEdit;
            riPictureEdit.SizeMode = DevExpress.XtraEditors.Controls.PictureSizeMode.Stretch;
            // Specifies the width of the Picture column.
            gridView1.Columns["Picture"].Width = 250;
            gridView1.Columns["Picture"].OptionsColumn.FixedWidth = true;

            // Enables automatic height for master rows.
            gridView1.OptionsView.RowAutoHeight = true;

            // Creates columns in a detail pattern view (CardView) for all fields in the Products table.
            cardViewProducts.PopulateColumns(dataSetNwind.Tables["Products"]);
            // Hides the CategoryID column.
            cardViewProducts.Columns["CategoryID"].VisibleIndex = -1;
            // Formats cell values in the UnitPrice column as currency.
            cardViewProducts.Columns["UnitPrice"].DisplayFormat.FormatType = FormatType.Numeric;
            cardViewProducts.Columns["UnitPrice"].DisplayFormat.FormatString = "c2";
        }
    }
}
vb
Imports System.Data
Imports System.Data.OleDb
Imports DevExpress.Utils
Imports DevExpress.XtraEditors.Repository
Imports DevExpress.XtraGrid.Views.Card

Namespace DXDataGridMasterDetailApp
    Partial Public Class Form1
        Inherits DevExpress.XtraEditors.XtraForm

        Public Sub New()
            InitializeComponent()
            ' Creates a connection to the Nwind database.
            Dim connection As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source = C:\Users\Public\Documents\DevExpress Demos 22.2\Components\Data\nwind.mdb")
            ' Creates the data adapters to retrieve data from the Categories and Products data tables.
            Dim adapterCategories As New OleDbDataAdapter("SELECT CategoryID, CategoryName, Picture FROM Categories", connection)
            Dim adapterProducts As New OleDbDataAdapter("SELECT CategoryID, ProductID, ProductName, UnitPrice FROM Products", connection)

            Dim dataSetNwind As New DataSet()
            ' Creates DataTable objects that correspond to database tables.
            adapterCategories.Fill(dataSetNwind, "Categories")
            adapterProducts.Fill(dataSetNwind, "Products")

            ' Sets up a master-detail relationship between data tables.
            Dim keyColumn As DataColumn = dataSetNwind.Tables("Categories").Columns("CategoryID")
            Dim foreignKeyColumn As DataColumn = dataSetNwind.Tables("Products").Columns("CategoryID")
            dataSetNwind.Relations.Add("CategoriesProducts", keyColumn, foreignKeyColumn)

            ' Binds the Data Grid to a data source.
            gridControl1.DataSource = dataSetNwind.Tables("Categories")
            ' Forces the Data Grid to initialize its settings.
            gridControl1.ForceInitialize()

            ' Creates a pattern view (CardView) to display detail data.
            Dim cardViewProducts As New CardView(gridControl1)
            gridControl1.LevelTree.Nodes.Add("CategoriesProducts", cardViewProducts)
            ' Specifies the detail view's caption (the caption of detail tabs).
            cardViewProducts.ViewCaption = "Category Products"

            ' Hides the CategoryID column from the master view.
            gridView1.Columns("CategoryID").VisibleIndex = -1

            ' Creates a Picture Edit repository item to display images in the Picture column,
            ' adds it to the Data Grid's RepositoryItems collection, and sets up image settings.
            Dim riPictureEdit As RepositoryItemPictureEdit = TryCast(gridControl1.RepositoryItems.Add("PictureEdit"), RepositoryItemPictureEdit)
            gridView1.Columns("Picture").ColumnEdit = riPictureEdit
            riPictureEdit.SizeMode = DevExpress.XtraEditors.Controls.PictureSizeMode.Stretch
            ' Specifies the width of the Picture column.
            gridView1.Columns("Picture").Width = 250
            gridView1.Columns("Picture").OptionsColumn.FixedWidth = True

            ' Enables automatic height for master rows.
            gridView1.OptionsView.RowAutoHeight = True

            ' Creates columns in a detail pattern view (CardView) for all fields in the Products table.
            cardViewProducts.PopulateColumns(dataSetNwind.Tables("Products"))
            ' Hides the CategoryID column.
            cardViewProducts.Columns("CategoryID").VisibleIndex = -1
            ' Formats cell values in the UnitPrice column as currency.
            cardViewProducts.Columns("UnitPrice").DisplayFormat.FormatType = FormatType.Numeric
            cardViewProducts.Columns("UnitPrice").DisplayFormat.FormatString = "c2"
        End Sub
    End Class
End Namespace

The screenshot below shows the result:

Bind to Objects with Collection Properties

The Data Grid handles properties of the IList type (for example, ArrayList, List<T>) as detail views if its GridOptionsDetail.EnableMasterViewMode option is enabled.

Note

The Data Grid does not handle properties of the IList<T>, ICollection<T>, and IEnumerable types as collections. Enable the View’s BaseView.DataController.AllowIEnumerableDetails option before you bind the Data Grid to a data source to handle these properties as collections.

The following example binds the Data Grid to ArrayList (Orders). Data source items are objects of the Order type with the Products collection property. The Data Grid automatically identifies and visualizes a master-detail relation.

csharp
using System;
using System.Collections;
using System.ComponentModel.DataAnnotations;

namespace DXDataGridMasterDetailApp {
    public partial class Form1 : DevExpress.XtraEditors.XtraForm {
        public Form1() {
            InitializeComponent();
            // Binds the Data Grid to a data source (ArrayList descendant).
            gridControl1.DataSource = new Orders();
        }
    }
    public class Orders : ArrayList {
        public Orders() {
            Add(new Order(new ArrayList() {
                new Product(){ ProductName = "Product A-1", Price = 78.99 },
                new Product(){ ProductName = "Product A-2", Price = 199.99 },
                new Product(){ ProductName = "Product A-3", Price = 18.99 },
            }) { OrderName = "Order A", OrderDate = DateTime.Today } );
            Add(new Order(new ArrayList() {
                new Product(){ ProductName = "Product B-1", Price = 25.99 },
                new Product(){ ProductName = "Product B-2", Price = 277.99 },
                new Product(){ ProductName = "Product B-3", Price = 10.99 },
            }) { OrderName = "Order B", OrderDate = DateTime.Today });
            Add(new Order(new ArrayList() {
                new Product(){ ProductName = "Product C-1", Price = 5.99 },
                new Product(){ ProductName = "Product C-2", Price = 14.99 },
                new Product(){ ProductName = "Product C-3", Price = 77.99 },
            }) { OrderName = "Order C", OrderDate = DateTime.Today });
        }
        public virtual new Order this[int index] {
            get {
                return (Order)base[index];
            }
        }
    }
    public class Order {
        ArrayList products;
        public Order(ArrayList productsList) {
            products = productsList;
        }
        public string OrderName { get; set; }
        public DateTime OrderDate { get; set; }
        public ArrayList Products { 
            get { return products; }
            set { products = value; }
        }
    }
    public class Product {
        public string ProductName { get; set; }
        [DisplayFormat(DataFormatString = "c2")]
        public double Price { get; set; }
    }
}
vb
Imports System
Imports System.Collections
Imports System.ComponentModel.DataAnnotations

Namespace DXDataGridMasterDetailApp
    Partial Public Class Form1
        Inherits DevExpress.XtraEditors.XtraForm

        Public Sub New()
            InitializeComponent()
            ' Binds the Data Grid to a data source (ArrayList descendant).
            gridControl1.DataSource = New Orders()
        End Sub
    End Class
    Public Class Orders
        Inherits ArrayList

        Public Sub New()
            Add(New Order(New ArrayList() From {
                New Product() With {.ProductName = "Product A-1", .Price = 78.99},
                New Product() With {.ProductName = "Product A-2", .Price = 199.99},
                New Product() With {.ProductName = "Product A-3", .Price = 18.99}
            }) { OrderName = "Order A", OrderDate = Date.Today })
            Add(New Order(New ArrayList() From {
                New Product() With {.ProductName = "Product B-1", .Price = 25.99},
                New Product() With {.ProductName = "Product B-2", .Price = 277.99},
                New Product() With {.ProductName = "Product B-3", .Price = 10.99}
            }) { OrderName = "Order B", OrderDate = Date.Today })
            Add(New Order(New ArrayList() From {
                New Product() With {.ProductName = "Product C-1", .Price = 5.99},
                New Product() With {.ProductName = "Product C-2", .Price = 14.99},
                New Product() With {.ProductName = "Product C-3", .Price = 77.99}
            }) { OrderName = "Order C", OrderDate = Date.Today })
        End Sub
        Default Public Overridable Shadows ReadOnly Property Item(ByVal index As Integer) As Order
            Get
                Return DirectCast(MyBase.Item(index), Order)
            End Get
        End Property
    End Class
    Public Class Order
        Private products_Renamed As ArrayList
        Public Sub New(ByVal productsList As ArrayList)
            products_Renamed = productsList
        End Sub
        Public Property OrderName() As String
        Public Property OrderDate() As Date
        Public Property Products() As ArrayList
            Get
                Return products_Renamed
            End Get
            Set(ByVal value As ArrayList)
                products_Renamed = value
            End Set
        End Property
    End Class
    Public Class Product
        Public Property ProductName() As String
        <DisplayFormat(DataFormatString := "c2")>
        Public Property Price() As Double
    End Class
End Namespace

The image below shows the result:

Load Details on Demand (Handle Events)

Primary Events

Handle the following events to load detail data:

|

Event Name

|

Description

| | --- | --- | |

MasterRowGetRelationCount

|

Fires for every master row and allows you to specify the number of details. Use the e.RowHandle parameter to identify a master row. Set the e.RelationCount parameter to the number of detail tables (views).

The Data Grid may fire the MasterRowGetRelationCount event with the e.RowHandle equal to GridControl.InvalidRowHandle. This is a service event that allows you to display or hide all master row expand/collapse buttons. Set the e.RelationCount to any positive number to display expand/collapse buttons.

csharp
gridView.MasterRowGetRelationCount += (s, e) => {
    e.RelationCount = 1;
};
vb
AddHandler gridView.MasterRowGetRelationCount, Sub(s, e)
    e.RelationCount = 1
End Sub

| |

MasterRowEmpty

|

Fires for each master with details. The MasterRowEmpty event can fire multiple times to the same master row. It depends on how many related details the master row has. The AllowExpandEmptyDetails option must be disabled.

Use the e.RelationIndex parameter to get the processed detail. Set the e.IsEmpty parameter to true if the detail is empty. The Data Grid does not display empty detail views.

csharp
gridView.MasterRowGetRelationCount += (s, e) => {
    e.RelationCount = 2;
};

gridView.MasterRowEmpty += (s, e) => {
    e.IsEmpty = false;
};
vb
AddHandler gridView.MasterRowGetRelationCount, Sub(s, e) e.RelationCount = 2

AddHandler gridView.MasterRowEmpty, Sub(s, e)
    e.IsEmpty = False
End Sub

| |

MasterRowGetRelationName

|

Handle this event to specify the relation name for each detail. Use the e.RelationName parameter to specify the relation name.

Important

The level name must exactly match the name and case of the master-detail relation with which the view is associated. If a detail view displays data from a collection property, the level name must match the collection name.

csharp
gridView1.MasterRowGetRelationName += (s, e) => {
    switch (e.RelationIndex) {
        case 0:
            e.RelationName = "Order Detail";
            break;
        case 1:
            e.RelationName = "Customer Details";
            break;
    }
};
vb
AddHandler gridView1.MasterRowGetRelationName, Sub(s, e)
    Select Case e.RelationIndex
        Case 0
            e.RelationName = "Order Detail"
        Case 1
            e.RelationName = "Customer Details"
    End Select
End Sub

| |

MasterRowGetChildList

|

The Data Grid dynamically creates a clone view based on the settings of its pattern view when a user expands a master row (a clone view is a copy of a pattern view). Use the e.ChildList property to supply data to clone views.

csharp
gridView.MasterRowGetChildList += (s, e) => {
    e.ChildList = e.RelationIndex == 0 ? SampleData1.GetData().ToList() : SampleData2.GetData().ToList();
};
vb
AddHandler gridView.MasterRowGetChildList, Sub(s, e) e.ChildList = If(e.RelationIndex = 0, SampleData1.GetData().ToList(), SampleData2.GetData().ToList())

|

Run Demo: Master-Detail Mode with Events

Optional Events

Example: How to Display Master-Detail Data on Events

csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace DXDataGridMasterDetailApp {
    public partial class Form1 : DevExpress.XtraEditors.XtraForm {
        public Form1() {
            InitializeComponent();
            gridControl1.DataSource = SampleData.GetData();

            gridView1.MasterRowGetRelationCount += (s, e) => {
                e.RelationCount = 1;
            };

            gridView1.MasterRowEmpty += (s, e) => {
                e.IsEmpty = false;
            };

            gridView1.MasterRowGetChildList += (s, e) => {
                e.ChildList = SampleData.GetData();
            };

            gridView1.MasterRowGetRelationName += (s, e) => {
                e.RelationName = "Test Relation Name";
            };
        }
    }
    public class SampleData {
        [Display(Order = -1)]
        public int ID { get; set; }
        public string Name { get; set; }
        public double Length { get; set; }
        public bool Mark { get; set; }
        public DateTime RecordDate { get; set; }
        public static List<SampleData> GetData() {
            return new List<SampleData>() {
                new SampleData(){ ID = 0, Name = "Bluehead Wrasse", Length = 15.09, Mark = false, RecordDate = DateTime.Now },
                new SampleData(){ ID = 1, Name = "Ornate ButterflyFish", Length = 15.09, Mark = true, RecordDate = DateTime.Now },
                new SampleData(){ ID = 2, Name = "Senorita", Length = 15.09, Mark = true, RecordDate = DateTime.Now },
                new SampleData(){ ID = 3, Name = "Surf Smelt", Length = 15.09, Mark = false, RecordDate = DateTime.Now },
            };
        }
    }
}
vb
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.ComponentModel.DataAnnotations

Namespace DXDataGridMasterDetailApp
    Partial Public Class Form1
        Inherits DevExpress.XtraEditors.XtraForm

        Public Sub New()
            InitializeComponent()
            gridControl1.DataSource = SampleData.GetData()

            AddHandler gridView1.MasterRowGetRelationCount, Sub(s, e) e.RelationCount = 1

            AddHandler gridView1.MasterRowEmpty, Sub(s, e) e.IsEmpty = False

            AddHandler gridView1.MasterRowGetChildList, Sub(s, e) e.ChildList = SampleData.GetData()

            AddHandler gridView1.MasterRowGetRelationName, Sub(s, e) e.RelationName = "Test Relation Name"
        End Sub
    End Class
    Public Class SampleData
        <Display(Order := -1)>
        Public Property ID() As Integer
        Public Property Name() As String
        Public Property Length() As Double
        Public Property Mark() As Boolean
        Public Property RecordDate() As Date
        Public Shared Function GetData() As List(Of SampleData)
            Return New List(Of SampleData)() From {
                New SampleData() With {.ID = 0, .Name = "Bluehead Wrasse", .Length = 15.09, .Mark = False, .RecordDate = Date.Now},
                New SampleData() With {.ID = 1, .Name = "Ornate ButterflyFish", .Length = 15.09, .Mark = True, .RecordDate = Date.Now},
                New SampleData() With {.ID = 2, .Name = "Senorita", .Length = 15.09, .Mark = True, .RecordDate = Date.Now},
                New SampleData() With {.ID = 3, .Name = "Surf Smelt", .Length = 15.09, .Mark = False, .RecordDate = Date.Now}
            }
        End Function
    End Class
End Namespace

The screenshot below shows the result:

Load Details Dynamically by Implementing IRelationList, IRelationListEx

This is an alternative to handling master-detail events. Implement the IRelationList or IRelationListEx interface in a data source or .NET data structure (for example, System.Data.DataView). The IRelationList interface exposes the following properties and methods:

The IRelationListEx interface extends the IRelationList with the following methods that allow you to specify the number of master-detail relationships and detail caption for master rows:

Example: How to Implement IRelationList (Master-Detail)

csharp
public class NWTables : ArrayList, DevExpress.Data.IRelationList {
    private DataSet dataSet;
    public NWTables(DataSet ds) {
        Add(new NWTable(Properties.Resources.SuppliersTable, "Suppliers"));
        Add(new NWTable(Properties.Resources.CategoriesTable, "Categories"));
        Add(new NWTable(Properties.Resources.EmployeesTable, "Employees"));
        Add(new NWTable(Properties.Resources.ShippersTable, "Shippers"));
        Add(new NWTable(Properties.Resources.CustomersTable, "Customers"));
        Add(new NWTable(Properties.Resources.OrdersTable, "Orders500"));
        dataSet = ds;
    }
    string IRelationList.GetRelationName(int index, int relationIndex) {
        if(index >= 0 && index < this.Count)
            return this[index].RelationName;
        return "";
    }
    int IRelationList.RelationCount {
        get { return dataSet.Tables.Count > 0 ? 1 : 0; }
    }
    IList IRelationList.GetDetailList(int index, int relationIndex) {
        if(dataSet.Tables.Count > 0) 
            return dataSet.Tables[((NWTable)this[index]).TableName()].DefaultView;
        return null;
    }
    bool IRelationList.IsMasterRowEmpty(int index, int relationIndex) {
        return false;
    }
    public virtual new NWTable this[int index] {
        get {return (NWTable)(base[index]);}
    }
}
vb
Public Class NWTables
  Inherits ArrayList
  Implements DevExpress.Data.IRelationList

  Private dataSet As DataSet
  Public Sub New(ByVal ds As DataSet)
    Add(New NWTable(My.Resources.SuppliersTable, "Suppliers"))
    Add(New NWTable(My.Resources.CategoriesTable, "Categories"))
    Add(New NWTable(My.Resources.EmployeesTable, "Employees"))
    Add(New NWTable(My.Resources.ShippersTable, "Shippers"))
    Add(New NWTable(My.Resources.CustomersTable, "Customers"))
        Add(New NWTable(My.Resources.OrdersTable, "Orders500"))
    dataSet = ds
  End Sub
  Private Function IRelationList_GetRelationName(ByVal index As Integer, ByVal relationIndex As Integer) As String Implements IRelationList.GetRelationName
    If index >= 0 AndAlso index < Me.Count Then
      Return Me(index).RelationName
    End If
    Return ""
  End Function
  Private ReadOnly Property IRelationList_RelationCount() As Integer Implements IRelationList.RelationCount
    Get
      Return If(dataSet.Tables.Count > 0, 1, 0)
    End Get
  End Property
  Private Function IRelationList_GetDetailList(ByVal index As Integer, ByVal relationIndex As Integer) As IList Implements IRelationList.GetDetailList
    If dataSet.Tables.Count > 0 Then
      Return dataSet.Tables(CType(Me(index), NWTable).TableName()).DefaultView
    End If
    Return Nothing
  End Function
  Private Function IRelationList_IsMasterRowEmpty(ByVal index As Integer, ByVal relationIndex As Integer) As Boolean Implements IRelationList.IsMasterRowEmpty
    Return False
  End Function
  Default Public Overridable Shadows ReadOnly Property Item(ByVal index As Integer) As NWTable
    Get
      Return DirectCast(MyBase.Item(index), NWTable)
    End Get
  End Property
End Class

Create Multi-Level Relationships

The following example sets up the WinForms Data Grid control to visualize the Categories-Products-Order Details master-detail relationship.

csharp
using System;
using System.Windows.Forms;
using DevExpress.XtraEditors;
using DevExpress.XtraGrid;
using DevExpress.XtraGrid.Views.Base;
using DevExpress.XtraGrid.Views.Card;
using DevExpress.XtraGrid.Views.Grid;

namespace DXDataGridMasterDetailApp {
    public partial class Form1 : XtraForm {
        GridControl gridControl;
        public Form1() {
            InitializeComponent();
            gridControl = new GridControl();
            GridView mainView = new GridView(gridControl);
            GridView patternView1 = new GridView(gridControl);
            CardView patternView2 = new CardView(gridControl);

            GridLevelNode detailLevel1 = new GridLevelNode();
            detailLevel1.LevelTemplate = patternView1;
            detailLevel1.RelationName = "CategoriesProducts";

            GridLevelNode detailLevel2 = new GridLevelNode();
            detailLevel2.LevelTemplate = patternView2;
            detailLevel2.RelationName = "ProductsOrder Details";

            detailLevel1.Nodes.Add(detailLevel2);
            gridControl.LevelTree.Nodes.Add(detailLevel1);

            gridControl.MainView = mainView;
            gridControl.Name = "gridControl1";
            gridControl.Dock = DockStyle.Fill;

            gridControl.ViewCollection.AddRange(new BaseView[] { mainView, patternView1, patternView2 });
            this.Controls.Add(gridControl);
        }
        private void Form1_Load(object sender, EventArgs e) {
            this.order_DetailsTableAdapter1.Fill(this.nwindDataSet1.Order_Details);
            this.productsTableAdapter1.Fill(this.nwindDataSet1.Products);
            this.categoriesTableAdapter.Fill(this.nwindDataSet1.Categories);
            // Bind the Data Grid control to a data source.
            gridControl.DataSource = categoriesBindingSource;
        }
    }
}
vb
Imports System
Imports System.Windows.Forms
Imports DevExpress.XtraEditors
Imports DevExpress.XtraGrid
Imports DevExpress.XtraGrid.Views.Base
Imports DevExpress.XtraGrid.Views.Card
Imports DevExpress.XtraGrid.Views.Grid

Namespace DXDataGridMasterDetailApp
    Partial Public Class Form1
        Inherits XtraForm

        Private gridControl As GridControl
        Public Sub New()
            InitializeComponent()
            gridControl = New GridControl()
            Dim mainView As New GridView(gridControl)
            Dim patternView1 As New GridView(gridControl)
            Dim patternView2 As New CardView(gridControl)

            Dim detailLevel1 As New GridLevelNode()
            detailLevel1.LevelTemplate = patternView1
            detailLevel1.RelationName = "CategoriesProducts"

            Dim detailLevel2 As New GridLevelNode()
            detailLevel2.LevelTemplate = patternView2
            detailLevel2.RelationName = "ProductsOrder Details"

            detailLevel1.Nodes.Add(detailLevel2)
            gridControl.LevelTree.Nodes.Add(detailLevel1)

            gridControl.MainView = mainView
            gridControl.Name = "gridControl1"
            gridControl.Dock = DockStyle.Fill

            gridControl.ViewCollection.AddRange(New BaseView() { mainView, patternView1, patternView2 })
            Me.Controls.Add(gridControl)
        End Sub
        Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
            Me.order_DetailsTableAdapter1.Fill(Me.nwindDataSet1.Order_Details)
            Me.productsTableAdapter1.Fill(Me.nwindDataSet1.Products)
            Me.categoriesTableAdapter.Fill(Me.nwindDataSet1.Categories)
            ' Bind the Data Grid control to a data source.
            gridControl.DataSource = categoriesBindingSource
        End Sub
    End Class
End Namespace

The screenshot below shows the result:

Access Master and Clone Views

Access Main (Root) View

Use the Data Grid’s MainView property to get the top-level master view. The grid’s FocusedView property gets or sets the View which is currently focused.

Initialize and Access Clone Views

The Data Grid dynamically creates a clone view based on the settings of its pattern view when a user expands a master row (a clone view is a copy of a pattern view). The Data Grid destroys the clone view after the corresponding master row is collapsed.

The Data Grid fires the ViewRegistered event after it creates a clone view. Handle this event to customize the clone view before it is displayed.

csharp
gridControl1.ViewRegistered += (s, e) => {
    if(e.View.IsDetailView) {
        // Customize the clone view.
    }
};
vb
AddHandler gridControl1.ViewRegistered, Sub(s, e)
    If e.View.IsDetailView Then
        ' Customize the clone view.
    End If
End Sub

Use the master view’s GetDetailView(Int32, Int32) method to get a clone view for a specific master row.

The following example customizes appearance settings of even rows in a clone view based on a condition.

csharp
using DevExpress.XtraGrid.Views.Grid;

gridControl1.ViewRegistered += (s, e) => {
    if(e.View.IsDetailView) {
        (e.View as GridView).OptionsView.EnableAppearanceEvenRow = true;
    }
};
gridView1.MasterRowExpanded += (s, e) => {
    GridView masterView = s as GridView;
    GridView cloneView = masterView.GetDetailView(e.RowHandle, e.RelationIndex) as GridView;
    cloneView.Appearance.EvenRow.Options.UseBackColor = true;
    cloneView.Appearance.EvenRow.BackColor = cloneView.RowCount > 2 ? Color.LightGreen : Color.Orange;
};
vb
Imports DevExpress.XtraGrid.Views.Grid

AddHandler gridControl1.ViewRegistered, Sub(s, e)
    If e.View.IsDetailView Then
        TryCast(e.View, GridView).OptionsView.EnableAppearanceEvenRow = True
    End If
End Sub
AddHandler gridView1.MasterRowExpanded, Sub(s, e)
    Dim masterView As GridView = TryCast(s, GridView)
    Dim cloneView As GridView = TryCast(masterView.GetDetailView(e.RowHandle, e.RelationIndex), GridView)
    cloneView.Appearance.EvenRow.Options.UseBackColor = True
    cloneView.Appearance.EvenRow.BackColor = If(cloneView.RowCount > 2, Color.LightGreen, Color.Orange)
End Sub

The image below shows the result:

Get Visible Views

Use the Data Grid’s Views property to get a collection of all visible views. This collection includes a master (root) view and clone views displayed for expanded rows. The Data Grid automatically adds/removes clone views to/from this collection as rows expand or collapse.

Get Parent View

Use the view’s BaseView.ParentView property to get its parent view. For clone views, the ParentView property returns a master view. For the main and pattern views, the ParentView property returns null ( Nothing in Visual Basic).

Get Visible Clone View

Use the master view’s GridView.GetVisibleDetailView method to get the currently visible detail (a clone view) for the specified master row.

Master-Detail Level Tree

Use the Data Grid’s LevelTree property to get a collection of relation levels (GridLevelNode) - a ‘hierarchical’ structure of pattern views in a master-detail relationship.

csharp
using DevExpress.XtraGrid;
using DevExpress.XtraGrid.Views.Base;
using DevExpress.XtraGrid.Views.Grid;

// Binds the Data Grid to a data source.
gridControl.DataSource = Category.GetMasterDetailData();
// Creates a pattern view and customizes its appearance settings.
GridView detailPatternView = new GridView { ViewCaption = Category.ProductsLevelName };
detailPatternView.Appearance.Row.BackColor = Color.Coral;
detailPatternView.Appearance.Row.Options.UseBackColor = true;
// Adds the pattern view to the Data Grid's ViewCollection.
gridControl.ViewCollection.Add(detailPatternView);
// Associates the pattern view with the Products relation level.
gridControl.LevelTree.Nodes.Add(Category.ProductsLevelName, detailPatternView);
vb
Imports DevExpress.XtraGrid
Imports DevExpress.XtraGrid.Views.Base
Imports DevExpress.XtraGrid.Views.Grid

' Binds the Data Grid to a data source.
gridControl.DataSource = Category.GetMasterDetailData()
' Creates a pattern view and customizes its appearance settings.
Dim detailPatternView As GridView = New GridView With {.ViewCaption = Category.ProductsLevelName}
detailPatternView.Appearance.Row.BackColor = Color.Coral
detailPatternView.Appearance.Row.Options.UseBackColor = True
' Adds the pattern view to the Data Grid's ViewCollection.
gridControl.ViewCollection.Add(detailPatternView)
' Associates the pattern view with the Products relation level.
gridControl.LevelTree.Nodes.Add(Category.ProductsLevelName, detailPatternView)

Get Master Rows

Use a clone view’s SourceRow method to get its master row. The clone view’s SourceRowHandle property allows you to get the handle of its master row.

Get and Set Cell Values in Clone Views

Use the parent (master) view’s GetDetailView method to obtain a clone view (detail) for the specified expanded master row. Use the clone view’s data editing API to get and set cell values.

Expand and Collapse Master Rows

Use the parent view’s GridView.ExpandMasterRow and GridView.CollapseMasterRow methods to expand/collapse the specified master row. The CollapseAllDetails() method collapses all master rows in a view. You can also use the SetMasterRowExpanded method to expand/collapse a master row.

Expand and collapse API also includes the following methods:

Handle the MasterRowExpanding and MasterRowCollapsing events to prevent the user from expanding/collapsing certain rows.

The MasterRowExpanded and MasterRowCollapsed events notify that the user has expanded/collapsed a master row.

Example 1: Iterate Over Rows in Main and Clone Views

csharp
using DevExpress.XtraGrid.Views.Grid;
using DevExpress.XtraGrid.Views.Base;

public void NavigateDetails(ColumnView inspectedView) {
    if(inspectedView == null) return;
    // Prevent excessive visual updates. 
    inspectedView.BeginUpdate();
    try {
        GridView gridView = inspectedView as GridView;
        // Get the number of data rows in the View. 
        int dataRowCount;
        if(gridView == null)
            dataRowCount = inspectedView.RowCount;
        else 
            dataRowCount = gridView.DataRowCount;
        // Traverse the View's rows. 
        for(int rowHandle = 0; rowHandle < dataRowCount; rowHandle++) {
            // Place your code here to process the current row. 
            // ...                     
            if(gridView != null) {                        
                // Get the number of master-detail relationships for the current row. 
                int relationCount = gridView.GetRelationCount(rowHandle);                    
                // Iterate through master-detail relationships. 
                for(int relationIndex = 0; relationIndex < relationCount; relationIndex++) {
                    // Store expansion status of the corresponding detail View. 
                    bool wasExpanded = gridView.GetMasterRowExpandedEx(rowHandle, relationIndex);
                    // Expand the detail View. 
                    if(!wasExpanded)
                        gridView.SetMasterRowExpandedEx(rowHandle, relationIndex, true);
                    // Navigate the detail View. 
                    NavigateDetails((ColumnView)gridView.GetDetailView(rowHandle, relationIndex));
                    // Restore the row's expansion status. 
                    gridView.SetMasterRowExpandedEx(rowHandle, relationIndex, wasExpanded);
                }
            }
        }
    }
    finally {
        // Enable visual updates. 
        inspectedView.EndUpdate();
    }
}
vb
Imports DevExpress.XtraGrid.Views.Grid
Imports DevExpress.XtraGrid.Views.Base

Public Sub NavigateDetails(ByVal inspectedView As ColumnView)
    If inspectedView Is Nothing Then Return 
    ' Prevent excessive visual updates. 
    inspectedView.BeginUpdate()
    Try 
        Dim gridView As GridView = Nothing 
        If TypeOf inspectedView Is GridView Then gridView = inspectedView
        ' Get the number of data rows in the View. 
        Dim dataRowCount As Integer 
        If gridView Is Nothing Then 
            dataRowCount = inspectedView.RowCount
        Else 
            dataRowCount = gridView.DataRowCount
        End If 
        ' Traverse the View's rows. 
        Dim rowHandle As Integer 
        For rowHandle = 0 To dataRowCount - 1
            ' Place your code here to process the current row. 
            ' ... 
            Try 
                inspectedView.SetRowCellValue(rowHandle, inspectedView.Columns(0), rowHandle)
            Catch ex As Exception

            End Try 
            If Not gridView Is Nothing Then 
                ' Get the number of master-detail relationships for the current row. 
                Dim relationCount As Integer = gridView.GetRelationCount(rowHandle)
                ' Iterate through master-detail relationships. 
                Dim relationIndex As Integer 
                For relationIndex = 0 To relationCount - 1
                    ' Store expansion status of the corresponding detail View. 
                    Dim wasExpanded As Boolean = _
                      gridView.GetMasterRowExpandedEx(rowHandle, relationIndex)
                    ' Expand the detail View. 
                    If Not wasExpanded Then gridView.SetMasterRowExpandedEx( _
                      rowHandle, relationIndex, True)
                    ' Navigate the detail View. 
                    NavigateDetails(gridView.GetDetailView(rowHandle, relationIndex))
                    ' Restore the row's expansion status. 
                    gridView.SetMasterRowExpandedEx(rowHandle, relationIndex, wasExpanded)
                Next 
            End If 
        Next 

    Finally 
        ' Enable visual updates. 
        inspectedView.EndUpdate()
    End Try 
End Sub

Example 2: Expand a Master Row and Nesting Details

This example expands the focused row and all nested details.

Note

The OptionsDetail.AllowOnlyOneMasterRowExpanded property of detail views must be set to true.

csharp
using DevExpress.XtraGrid.Views.Grid;

public void RecursiveExpand(GridView masterView, int masterRowHandle) {
    try {
        var relationCount = masterView.GetRelationCount(masterRowHandle);
        for (var index = relationCount - 1; index >= 0; index--) {
            masterView.ExpandMasterRow(masterRowHandle, index);
            var childView = masterView.GetDetailView(masterRowHandle, index) as GridView;
            if (childView != null) {
                var childRowCount = childView.DataRowCount;
                for (var handle = 0; handle < childRowCount; handle++)
                    RecursiveExpand(childView, handle);
            }
        }
    }
    catch (Exception ex) { }
    finally { }
}
vb
Imports DevExpress.XtraGrid.Views.Grid
Imports DevExpress.XtraGrid.Views.Base

Public Sub RecursiveExpand(ByVal masterView As GridView, ByVal masterRowHandle As Integer)
    Try
        Dim relationCount = masterView.GetRelationCount(masterRowHandle)
        For index = relationCount - 1 To 0 Step -1
            masterView.ExpandMasterRow(masterRowHandle, index)
            Dim childView = TryCast(masterView.GetDetailView(masterRowHandle, index), GridView)
            If childView IsNot Nothing Then
                Dim childRowCount = childView.DataRowCount
                For handle = 0 To childRowCount - 1
                    RecursiveExpand(childView, handle)
                Next handle
            End If
        Next index
    Catch ex As Exception
    Finally
    End Try
End Sub

Example 3: Expand All Master Rows in a View

Note

The master view’s OptionsDetail.AllowOnlyOneMasterRowExpanded property must be set to true.

csharp
using DevExpress.XtraGrid.Views.Grid;

public void ExpandAllMasterRows(GridView View) {
    View.BeginUpdate();
    try {
        int dataRowCount = View.DataRowCount;
        for(int rHandle = 0; rHandle < dataRowCount; rHandle ++)
            View.SetMasterRowExpanded(rHandle, true);
    }
    finally {
        View.EndUpdate();
    }
}
vb
Imports DevExpress.XtraGrid.Views.Grid

Public Sub ExpandAllMasterRows(ByVal View As GridView)
    View.BeginUpdate()
    Try 
        Dim dataRowCount As Integer = View.DataRowCount
        Dim rHandle As Integer 
        For rHandle = 0 To dataRowCount - 1
            View.SetMasterRowExpanded(rHandle, True)
        Next 
    Finally 
        View.EndUpdate()
    End Try 
End Sub

Example 4: Expand All Master Rows and Detail Views

The following example expands all master rows and all detail/nested views after the grid has been loaded. The example uses ExpandAllMasterRows and RecursiveExpand methods implemented in Example 3 and Example 2 respectively. The ExpandAllMasterRows is modified to call the RecursiveExpand method:

Note

The OptionsDetail.AllowOnlyOneMasterRowExpanded property of master and detail views must be set to true.

csharp
private void gridControl1_Load(object sender, EventArgs e) {
    ExpandAllMasterRows(gridView1);
}

public void ExpandAllMasterRows(GridView View) {
    View.BeginUpdate();
    try {
        int dataRowCount = View.DataRowCount;
        for (int rHandle = 0; rHandle < dataRowCount; rHandle++) {
            View.SetMasterRowExpanded(rHandle, true);
            RecursiveExpand(View, rHandle);
        }
    }
    finally {
        View.EndUpdate();
    }
}

public void RecursiveExpand(GridView masterView, int masterRowHandle) {
    // ...    
}
vb
Private Sub gridControl1_Load(ByVal sender As Object, ByVal e As EventArgs)
    ExpandAllMasterRows(gridView1)
End Sub

Public Sub ExpandAllMasterRows(ByVal View As GridView)
    View.BeginUpdate()
    Try
        Dim dataRowCount As Integer = View.DataRowCount
        For rHandle As Integer = 0 To dataRowCount - 1
            View.SetMasterRowExpanded(rHandle, True)
            RecursiveExpand(View, rHandle)
        Next rHandle
    Finally
        View.EndUpdate()
    End Try
End Sub

Public Sub RecursiveExpand(ByVal masterView As GridView, ByVal masterRowHandle As Integer)
    ' ...    
End Sub

Additional Examples

Knowledge Base Articles

Cheat Sheets and Best Practices

Read the following quick-reference guide for general information and examples:

Master-Detail Mode - DevExpress WinForms Cheat Sheet

See Also

Master-Detail Relationships