如何在选中新行后折叠DataGrid中上次选中行的所有层级详情?
嵌套DataGrid点击新行自动折叠所有层级详情的实现方案
问题场景
我使用了多层嵌套的DataGrid,当前交互存在以下情况:
- 未选中任何行时:

- 点击第一行后,该行详情展开:

- 点击该行详情内的第一行后,内层行详情展开:

- 切换点击外层DataGrid的第二行后,第一行的层级详情并未折叠:

- 再次点击第一行时,会显示之前展开的两层行详情:

需求:实现点击新行时,自动折叠上次选中行的所有层级详情,支持任意多层嵌套的DataGrid。
现有XAML代码
<DataGrid x:Name="dataGrid1" ItemsSource="{Binding Students}" AutoGenerateColumns="False" IsReadOnly="True" HeadersVisibility="Column" Background="DarkBlue"> <DataGrid.Columns> <DataGridTextColumn Header="Name" Binding="{Binding Name}"/> <DataGridTextColumn Header="ID" Binding="{Binding ID}"/> <DataGridTextColumn Header="School" Binding="{Binding School}"/> </DataGrid.Columns> <DataGrid.RowDetailsTemplate> <DataTemplate> <DataGrid x:Name="nestedDataGrid1" ItemsSource="{Binding Teachers}" IsReadOnly="True" AutoGenerateColumns="False" HeadersVisibility="Column" Background="DarkRed"> <DataGrid.Columns> <DataGridTextColumn Header="Name" Binding="{Binding Name}"/> <DataGridTextColumn Header="ID" Binding="{Binding ID}"/> </DataGrid.Columns> <DataGrid.RowDetailsTemplate> <DataTemplate> <StackPanel Orientation="Vertical"> <TextBlock Text="Line 1" FontSize="30"/> <TextBlock Text="Line 2" FontSize="30"/> <TextBlock Text="Line 3" FontSize="30"/> <TextBlock Text="Line 4" FontSize="30"/> <TextBlock Text="Line 5" FontSize="30"/> </StackPanel> </DataTemplate> </DataGrid.RowDetailsTemplate> </DataGrid> </DataTemplate> </DataGrid.RowDetailsTemplate> </DataGrid>
解决方案
方案一:后台代码递归折叠(适合非MVVM场景)
通过监听外层DataGrid的选中变化,递归遍历并折叠之前选中行的所有嵌套DataGrid行详情。
- 给外层DataGrid添加
SelectionChanged事件:
<DataGrid x:Name="dataGrid1" ... SelectionChanged="DataGrid1_SelectionChanged">
- 后台实现递归折叠方法和事件处理:
private DataGridRow _lastSelectedRow; private void DataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e) { // 折叠上次选中行的所有层级详情 if (_lastSelectedRow != null) { _lastSelectedRow.DetailsVisibility = Visibility.Collapsed; var nestedGrid = FindVisualChild<DataGrid>(_lastSelectedRow); if (nestedGrid != null) CollapseAllNestedDetails(nestedGrid); } // 更新当前选中行记录 _lastSelectedRow = dataGrid1.SelectedItem != null ? dataGrid1.ItemContainerGenerator.ContainerFromItem(dataGrid1.SelectedItem) as DataGridRow : null; } // 递归折叠指定DataGrid下所有行的详情及嵌套DataGrid的内容 private void CollapseAllNestedDetails(DataGrid parentGrid) { foreach (var item in parentGrid.Items) { if (parentGrid.ItemContainerGenerator.ContainerFromItem(item) is DataGridRow row) { row.DetailsVisibility = Visibility.Collapsed; var nestedGrid = FindVisualChild<DataGrid>(row); if (nestedGrid != null) CollapseAllNestedDetails(nestedGrid); } } parentGrid.SelectedItem = null; } // 递归查找视觉树中的子DataGrid private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T typedChild) return typedChild; var result = FindVisualChild<T>(child); if (result != null) return result; } return null; }
方案二:MVVM绑定实现(适合MVVM架构)
通过给数据模型添加IsExpanded属性,绑定到行详情的可见性,在选中新项时递归关闭旧项的展开状态。
- 定义带展开状态的数据模型:
public class Student : INotifyPropertyChanged { public string Name { get; set; } public string ID { get; set; } public string School { get; set; } public ObservableCollection<Teacher> Teachers { get; set; } = new(); private bool _isExpanded; public bool IsExpanded { get => _isExpanded; set { _isExpanded = value; OnPropertyChanged(); // 折叠时同步关闭所有嵌套项的展开状态 if (!value) Teachers.ToList().ForEach(t => t.IsExpanded = false); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public class Teacher : INotifyPropertyChanged { public string Name { get; set; } public string ID { get; set; } private bool _isExpanded; public bool IsExpanded { get => _isExpanded; set { _isExpanded = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
- 修改XAML绑定行详情可见性:
<Window.Resources> <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> </Window.Resources> <DataGrid x:Name="dataGrid1" ItemsSource="{Binding Students}" SelectedItem="{Binding SelectedStudent}" ...> <DataGrid.RowStyle> <Style TargetType="DataGridRow"> <Setter Property="DetailsVisibility" Value="{Binding IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}"/> </Style> </DataGrid.RowStyle> <!-- 外层列定义 --> <DataGrid.RowDetailsTemplate> <DataTemplate> <DataGrid ItemsSource="{Binding Teachers}" ...> <DataGrid.RowStyle> <Style TargetType="DataGridRow"> <Setter Property="DetailsVisibility" Value="{Binding IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}"/> </Style> </DataGrid.RowStyle> <!-- 内层列定义 --> <!-- 内层行详情模板 --> </DataGrid> </DataTemplate> </DataGrid.RowDetailsTemplate> </DataGrid>
- ViewModel中处理选中项切换:
public class MainViewModel : INotifyPropertyChanged { public ObservableCollection<Student> Students { get; set; } = new(); private Student _selectedStudent; public Student SelectedStudent { get => _selectedStudent; set { // 关闭旧选中项的展开状态 if (_selectedStudent != null) _selectedStudent.IsExpanded = false; _selectedStudent = value; // 展开新选中项 if (_selectedStudent != null) _selectedStudent.IsExpanded = true; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
内容的提问来源于stack exchange,提问作者Jtastic
相关产品推荐
相关产品推荐

