WPF如何带表头在网格中显示ObservableCollection?
解决方案:实现绑定ObservableCollection的带表头网格布局
针对你的需求,提供两种实用方案,分别适配不同场景:
方案一:用ItemsControl实现完全自定义网格布局(推荐,无DataGrid冗余特性)
这种方式无需依赖DataGrid,完全通过嵌套ItemsControl实现行列布局,灵活度最高,适合仅展示、不需要DataGrid交互特性的场景。
1. 调整ViewModel结构(按行分组)
先定义行分组的ViewModel,方便绑定每行的表头和单元格数据:
// 单个指标的ViewModel(保持你原有的定义,补充必要属性) public class ReadOnlyIndicatorWithUnitsViewModel : INotifyPropertyChanged { public string Header { get; set; } public string Value { get; set; } public string Units { get; set; } // 实现INotifyPropertyChanged接口(省略具体代码) public event PropertyChangedEventHandler PropertyChanged; } // 行分组ViewModel public class IndicatorRowGroupViewModel : INotifyPropertyChanged { public string RowHeader { get; set; } public ObservableCollection<ReadOnlyIndicatorWithUnitsViewModel> RowIndicators { get; set; } public IndicatorRowGroupViewModel() { RowIndicators = new ObservableCollection<ReadOnlyIndicatorWithUnitsViewModel>(); } // 实现INotifyPropertyChanged接口(省略具体代码) public event PropertyChangedEventHandler PropertyChanged; } // 主ViewModel public class NavigationViewModel : INotifyPropertyChanged { public ObservableCollection<IndicatorRowGroupViewModel> IndicatorGridData { get; set; } public NavigationViewModel() { // 示例数据 IndicatorGridData = new ObservableCollection<IndicatorRowGroupViewModel> { new IndicatorRowGroupViewModel { RowHeader = "环境参数", RowIndicators = { new ReadOnlyIndicatorWithUnitsViewModel { Header = "温度", Value = "26", Units = "℃" }, new ReadOnlyIndicatorWithUnitsViewModel { Header = "湿度", Value = "58", Units = "%" } } }, new IndicatorRowGroupViewModel { RowHeader = "设备状态", RowIndicators = { new ReadOnlyIndicatorWithUnitsViewModel { Header = "转速", Value = "1450", Units = "rpm" }, new ReadOnlyIndicatorWithUnitsViewModel { Header = "压力", Value = "0.75", Units = "MPa" } } } }; } // 实现INotifyPropertyChanged接口(省略具体代码) public event PropertyChangedEventHandler PropertyChanged; }
2. XAML布局实现
通过外层ItemsControl渲染每行,内层ItemsControl渲染该行的所有单元格:
<Window.DataContext> <local:NavigationViewModel /> </Window.DataContext> <!-- 外层ItemsControl:渲染每一行 --> <ItemsControl ItemsSource="{Binding IndicatorGridData}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Margin="10" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="0,5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <!-- 行表头列 --> <ColumnDefinition Width="*" /> <!-- 单元格容器列 --> </Grid.ColumnDefinitions> <!-- 行表头 --> <TextBlock Grid.Column="0" Text="{Binding RowHeader}" FontWeight="Bold" Margin="5,2" VerticalAlignment="Center"/> <!-- 内层ItemsControl:渲染该行的所有单元格 --> <ItemsControl Grid.Column="1" ItemsSource="{Binding RowIndicators}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="1" Columns="{Binding RowIndicators.Count}" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <!-- 绑定你的自定义UserControl --> <local:ReadOnlyIndicatorWithUnits Header="{Binding Header}" Value="{Binding Value}" Units="{Binding Units}" Margin="3"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
方案二:自定义DataGrid模板(保留DataGrid交互特性)
如果需要DataGrid的排序、选择等原生特性,可以通过自定义列模板和分组样式实现,同时适配你的自定义UserControl。
1. XAML实现(含分组和自定义列)
<Window.DataContext> <local:NavigationViewModel /> </Window.DataContext> <!-- 定义分组用的CollectionViewSource --> <Window.Resources> <CollectionViewSource x:Key="GroupedIndicatorData" Source="{Binding IndicatorGridData}"> <CollectionViewSource.GroupDescriptions> <PropertyGroupDescription PropertyName="RowHeader" /> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> </Window.Resources> <DataGrid ItemsSource="{Binding Source={StaticResource GroupedIndicatorData}}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True" Margin="10"> <!-- 自定义分组表头样式 --> <DataGrid.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" FontWeight="Bold" Padding="5" Background="#F0F0F0"/> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </DataGrid.GroupStyle> <!-- 手动定义列,绑定自定义UserControl --> <DataGridTemplateColumn Header="温度"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <local:ReadOnlyIndicatorWithUnits Header="温度" Value="{Binding RowIndicators[0].Value}" Units="{Binding RowIndicators[0].Units}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn Header="湿度"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <local:ReadOnlyIndicatorWithUnits Header="湿度" Value="{Binding RowIndicators[1].Value}" Units="{Binding RowIndicators[1].Units}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <!-- 按需添加更多列 --> </DataGrid>
2. 动态列扩展(可选)
如果列数不固定,可以通过附加属性实现动态生成列:
// 自定义附加属性,用于DataGrid动态绑定列 public static class DataGridDynamicColumns { public static readonly DependencyProperty BindableColumnsProperty = DependencyProperty.RegisterAttached( "BindableColumns", typeof(ObservableCollection<DataGridColumn>), typeof(DataGridDynamicColumns), new UIPropertyMetadata(null, OnBindableColumnsChanged)); private static void OnBindableColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not DataGrid dataGrid) return; if (e.OldValue is ObservableCollection<DataGridColumn> oldCols) { foreach (var col in oldCols) dataGrid.Columns.Remove(col); oldCols.CollectionChanged -= ColumnsCollectionChanged; } if (e.NewValue is ObservableCollection<DataGridColumn> newCols) { foreach (var col in newCols) dataGrid.Columns.Add(col); newCols.CollectionChanged += ColumnsCollectionChanged; } } private static void ColumnsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (sender is not ObservableCollection<DataGridColumn> cols || e.Action == NotifyCollectionChangedAction.Reset) return; var dataGrid = cols.FirstOrDefault()?.DataGridOwner; if (dataGrid == null) return; switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (DataGridColumn col in e.NewItems) dataGrid.Columns.Add(col); break; case NotifyCollectionChangedAction.Remove: foreach (DataGridColumn col in e.OldItems) dataGrid.Columns.Remove(col); break; } } public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value) => element.SetValue(BindableColumnsProperty, value); public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element) => (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty); }
然后在ViewModel中添加列集合,动态生成列:
public ObservableCollection<DataGridColumn> DynamicColumns { get; set; } public NavigationViewModel() { DynamicColumns = new ObservableCollection<DataGridColumn>(); // 动态添加列示例 DynamicColumns.Add(new DataGridTemplateColumn { Header = "转速", CellTemplate = new DataTemplate { VisualTree = new FrameworkElementFactory(typeof(ReadOnlyIndicatorWithUnits)) { Setters = { new Setter(ReadOnlyIndicatorWithUnits.HeaderProperty, "转速"), new Setter(ReadOnlyIndicatorWithUnits.ValueProperty, new Binding("RowIndicators[0].Value")), new Setter(ReadOnlyIndicatorWithUnits.UnitsProperty, new Binding("RowIndicators[0].Units")) } } } }); }
最后在XAML中使用:
<DataGrid local:DataGridDynamicColumns.BindableColumns="{Binding DynamicColumns}" ItemsSource="{Binding Source={StaticResource GroupedIndicatorData}}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True" Margin="10"> <!-- 分组样式同上 --> </DataGrid>
内容的提问来源于stack exchange,提问作者Curtwagner1984
相关产品推荐
相关产品推荐

