wpf-119851-controls-and-libraries-data-grid-master-detail-data-grid-in-details.md
This topic describes how to display detail data within nested grids.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MasterDetailDemo"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
x:Class="MasterDetailDemo.MainWindow"
Title="Main Window">
<Window.Resources>
<dx:EntitySimpleDataSource x:Key="EntitySimpleDataSource"
ContextType="{x:Type local:NORTHWNDEntities}"
Path="Categories"/>
</Window.Resources>
<Grid>
<dxg:GridControl AutoGenerateColumns="AddNew"
ItemsSource="{Binding Data, Source={StaticResource EntitySimpleDataSource}}">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourceBinding="{Binding Products}">
<dxg:DataControlDetailDescriptor.DataControl>
<dxg:GridControl AutoGenerateColumns="AddNew"/>
</dxg:DataControlDetailDescriptor.DataControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
</Grid>
</Window>
View Example: Create a Master-Detail Grid
The master GridControl creates a copy of the DataControlDetailDescriptor.DataControl object for each expanded master row. In the detail GridControl / TableView object’s event handlers, use the e.Source property to obtain the current detail grid.
<dxg:GridControl AutoGenerateColumns="AddNew" ItemsSource="{Binding Items}">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourcePath="Items">
<dxg:GridControl AutoGenerateColumns="AddNew">
<dxg:GridControl.View>
<dxg:TableView x:Name="tableView1" CellValueChanging="CellValueChanging" />
</dxg:GridControl.View>
</dxg:GridControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
void CellValueChanging(object sender, CellValueChangedEventArgs e) {
e.Source.PostEditor();
}
Sub CellValueChanging(ByVal sender As Object, ByVal e As CellValueChangedEventArgs)
e.Source.PostEditor()
End Sub
Note
The GridViewBase.AddingNewRow event uses arguments of the standard AddingNewEventArgs type. This type does not contain the Source property. Use the sender parameter in the event handler.
The detail GridControl does not raise the following events:
To process these actions, handle root grid events and use the DataViewBase.FocusedView property to obtain the active detail view:
<dxg:GridControl ItemsSource="{Binding Path=Data}" AutoGenerateColumns="AddNew">
<dxg:GridControl.View>
<dxg:TableView PreviewMouseDown="TableView_PreviewMouseDown"/>
</dxg:GridControl.View>
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourceBinding="{Binding Data}">
<dxg:GridControl AutoGenerateColumns="AddNew"/>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
private void TableView_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
if (e.ChangedButton != System.Windows.Input.MouseButton.Right)
return;
var masterView = e.Source as TableView;
if (masterView == null)
return;
var activeView = (TableView)masterView.FocusedView;
var activeGrid = activeView.Grid;
if (activeGrid.GetMasterGrid() == null)
return;
activeGrid.SetCellValue(activeView.FocusedRowHandle, activeGrid.Columns[nameof(DataItem.Ready)], true);
}
Private Sub TableView_PreviewMouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
If e.ChangedButton <> System.Windows.Input.MouseButton.Right Then Return
Dim masterView = TryCast(e.Source, TableView)
If masterView Is Nothing Then Return
Dim activeView = CType(masterView.FocusedView, TableView)
Dim activeGrid = activeView.Grid
If activeGrid.GetMasterGrid() Is Nothing Then Return
activeGrid.SetCellValue(activeView.FocusedRowHandle, activeGrid.Columns(NameOf(DataItem.Ready)), True)
End Sub
In master-detail mode, the master row is the data context for the detail GridControl.
The View.DataContext.[YourPropertyName] binding path allows you to access the master row’s properties.
<dxg:GridControl ItemsSource="{Binding Customers}" AutoGenerateColumns="AddNew">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourcePath="Orders">
<dxg:GridControl AutoGenerateColumns="AddNew">
<dxg:GridColumn FieldName="LastName">
<dxg:GridColumn.CellTemplate>
<DataTemplate>
<dxe:TextEdit x:Name="PART_Editor"
EditValue="{Binding View.DataContext.LastName}"/>
</DataTemplate>
</dxg:GridColumn.CellTemplate>
</dxg:GridColumn>
</dxg:GridControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
You can bind the CurrentItem, SelectedItem, and SelectedItems properties of the detail grid in one way mode as demonstrated in the following example:
View Example: Bind Master and Detail Focused Rows to View Model Properties
<StackPanel>
<TextBlock Text="{Binding Level1CurrentItem.Name, FallbackValue=NONE}"/>
<TextBlock Text="{Binding Level2CurrentItem.Name, FallbackValue=NONE}"/>
<TextBlock Text="{Binding Level3CurrentItem.Name, FallbackValue=NONE}"/>
</StackPanel>
<dxg:GridControl ItemsSource="{Binding Data}"
AutoGenerateColumns="AddNew"
CurrentItem="{Binding Level1CurrentItem}">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourceBinding="{Binding Items}">
<dxg:GridControl AutoGenerateColumns="AddNew"
CurrentItem="{Binding Level2CurrentItem}">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourceBinding="{Binding Items}">
<dxg:GridControl AutoGenerateColumns="AddNew"
CurrentItem="{Binding Level3CurrentItem}"/>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
If you need to define selected and focused detail items in the View Model, your detail objects should contain information about their master items. Assign the DataControlDetailDescriptor.ParentPath property to the detail data source field that contains master objects. In this case, the GridControl can determine the master item associated with the detail item and select the specified row:
View Example: Define the Selected Detail Row in the View Model
<dxg:GridControl AutoGenerateColumns="AddNew"
ItemsSource="{Binding Items}"
CurrentItem="{Binding CurrentMasterItem}">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourcePath="DetailItems" ParentPath="MasterItem">
<dxg:DataControlDetailDescriptor.DataControl>
<dxg:GridControl AutoGenerateColumns="AddNew"
CurrentItem="{Binding CurrentDetailItem}"/>
</dxg:DataControlDetailDescriptor.DataControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
<StackPanel Grid.Row="1">
<TextBlock Margin="4,0,0,0" Text="Selected detail item from the master item:"/>
<dxe:ComboBoxEdit IsTextEditable="False"
ItemsSource="{Binding CurrentMasterItem.DetailItems}"
SelectedItem="{Binding CurrentDetailItem}"
DisplayMember="DetailName"/>
</StackPanel>
public class MainViewModel : ViewModelBase {
public MainViewModel() {
Items = new ObservableCollection<MasterLevelItem>();
InitMasterItems();
CurrentMasterItem = null;
}
public ObservableCollection<MasterLevelItem> Items { get; private set; }
public MasterLevelItem CurrentMasterItem { get { return GetValue<MasterLevelItem>(); } set { SetValue(value); } }
public DetailLevelItem CurrentDetailItem { get { return GetValue<DetailLevelItem>(); } set { SetValue(value); } }
// ...
}
public class MasterLevelItem {
public int MasterId { get; set; }
public string MasterName { get; set; }
public List<DetailLevelItem> DetailItems { get; set; }
public MasterLevelItem() {
DetailItems = new List<DetailLevelItem>();
InitDetailItems();
}
void InitDetailItems() {
for(int i = 0; i < 10; i++) {
DetailItems.Add(new DetailLevelItem() {
DetailId = i,
DetailName = String.Format("detail item {0}", i),
MasterItem = this,
});
}
}
}
Public Class MainViewModel
Inherits ViewModelBase
Private _Items As ObservableCollection(Of MultiLevelMasterDetail.MasterLevelItem)
Public Sub New()
Items = New ObservableCollection(Of MasterLevelItem)()
InitMasterItems()
CurrentMasterItem = Nothing
End Sub
Public Property Items As ObservableCollection(Of MasterLevelItem)
Get
Return _Items
End Get
Private Set(ByVal value As ObservableCollection(Of MasterLevelItem))
_Items = value
End Set
End Property
Public Property CurrentMasterItem As MasterLevelItem
Get
Return GetValue(Of MasterLevelItem)()
End Get
Set(ByVal value As MasterLevelItem)
SetValue(value)
End Set
End Property
Public Property CurrentDetailItem As DetailLevelItem
Get
Return GetValue(Of DetailLevelItem)()
End Get
Set(ByVal value As DetailLevelItem)
SetValue(value)
End Set
End Property
End Class
Public Class MasterLevelItem
Public Property MasterId As Integer
Public Property MasterName As String
Public Property DetailItems As List(Of DetailLevelItem)
Public Sub New()
DetailItems = New List(Of DetailLevelItem)()
InitDetailItems()
End Sub
Private Sub InitDetailItems()
For i As Integer = 0 To 10 - 1
DetailItems.Add(New DetailLevelItem() With {
.DetailId = i,
.DetailName = String.Format("detail item {0}", i),
.MasterItem = Me
})
Next
End Sub
End Class
The master grid’s search panel searches master data only. You can change this behavior and include detail data in search operations. To do this, specify the detail grid’s SearchPanelHighlightResults and SearchPanelAllowFilter properties.
Run Demo: Search Panel - Master-Detail Mode
The following code sample highlights search results in both master and detail grids:
<dxg:GridControl ... >
<dxg:GridControl.View>
<dxg:TableView SearchString="an"
SearchPanelAllowFilter="False"
SearchPanelHighlightResults="True">
</dxg:TableView>
</dxg:GridControl.View>
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourceBinding="{Binding Customers}">
<dxg:GridControl AutoGenerateColumns="AddNew">
<dxg:GridControl.View>
<dxg:TableView SearchPanelAllowFilter="False"
SearchPanelHighlightResults="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
You can also use the SearchPanelFindFilter property to specify a different comparison operator for the detail grid.
You can use the DetailHeaderContent property to specify the detail header. This header is displayed above the detail GridControl (if you set the DetailDescriptorBase.ShowHeader property to true), in the group panel, filter panel, and detail tab headers:
<dxg:GridControl ItemsSource="{Binding Source}"
AutoGenerateColumns="AddNew">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourcePath="Orders"
ShowHeader="True">
<dxg:GridControl AutoGenerateColumns="AddNew">
<dxg:GridControl.View>
<dxg:TableView DetailHeaderContent="Orders"/>
</dxg:GridControl.View>
</dxg:GridControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
The DetailDescriptorBase.ContentTemplate property allows you to specify content displayed above the detail GridControl. The template data context is the master row object:
<dxg:GridControl ItemsSource="{Binding Source}"
AutoGenerateColumns="AddNew">
<dxg:GridControl.DetailDescriptor>
<dxg:DataControlDetailDescriptor ItemsSourcePath="Orders">
<dxg:DataControlDetailDescriptor.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Notes}"
TextWrapping="Wrap"
Padding="4"/>
</DataTemplate>
</dxg:DataControlDetailDescriptor.ContentTemplate>
<dxg:GridControl AutoGenerateColumns="AddNew">
<dxg:GridControl.View>
<dxg:TableView AutoWidth="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</dxg:DataControlDetailDescriptor>
</dxg:GridControl.DetailDescriptor>
</dxg:GridControl>
The table below describes the difference between the DataControlDetailDescriptor and TreeListView.
| |
Master-Detail (DataControlDetailDescriptor)
|
TreeListView
| | --- | --- | --- | |
Data operations, settings synchronization
|
|
| |
Provide data for details
|
Use the following properties:
|
Use the following approaches:
| |
Group Data at runtime
|
Yes
|
No
| |
Display a multi-level structure
|
Set the detail grid’s GridControl.DetailDescriptor property to display one more level.
|
The TreeListView creates child nodes according to its configuration in bound mode.
| |
Display a different number of levels for different master rows
|
The total number of levels is fixed. Use the IsDetailButtonVisibleBinding property to hide expand buttons for certain items.
|
The number of levels is not limited. Different nodes can have a different number of levels.
|