WPF DataGrid动态列与动态绑定单元格模板的优化方案问询
最优实现方案
方案一:自定义DynamicPropertyColumn(推荐)
直接继承DataGridBoundColumn,根据PropertyWrapper的元数据动态生成单元格模板,同时内置属性存在性判断逻辑,完全符合MVVM规范,无需拼接XAML字符串。
1. 核心代码实现
public class DynamicPropertyColumn : DataGridBoundColumn { public PropertyWrapper PropertyWrapper { get; set; } protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) { var elementWrapper = dataItem as ElementWrapper; if (elementWrapper == null || !elementWrapper.HasProperty(PropertyWrapper.PropertyName)) { // 无对应属性时显示#N/A return new TextBlock { Text = "#N/A", HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; } // 布尔类型属性渲染CheckBox if (PropertyWrapper.PropertyType == typeof(bool)) { var checkBox = new CheckBox { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; // 双向绑定到ElementWrapper的属性值 var binding = new Binding($"Properties[{PropertyWrapper.PropertyName}].Value") { Mode = BindingMode.TwoWay, Source = elementWrapper }; checkBox.SetBinding(CheckBox.IsCheckedProperty, binding); // 如需业务逻辑,绑定Command替代事件(MVVM方式) // checkBox.Command = PropertyWrapper.ToggleValueCommand; return checkBox; } // 其他类型默认渲染文本 var textBlock = new TextBlock(); textBlock.SetBinding(TextBlock.TextProperty, new Binding($"Properties[{PropertyWrapper.PropertyName}].Value") { Source = elementWrapper }); return textBlock; } protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) { // 编辑模式复用生成逻辑,按需自定义 return GenerateElement(cell, dataItem); } }
2. 动态创建列的逻辑
在View的代码后台(属于UI层逻辑,不违反MVVM)添加列:
foreach (var propWrapper in ViewModel.PropertyWrappers) { DataGrid.Columns.Add(new DynamicPropertyColumn { Header = propWrapper.DisplayName, PropertyWrapper = propWrapper }); }
方案二:DataTemplateSelector结合预定义模板
如果不想自定义列,可提前在XAML中定义模板,通过选择器根据数据上下文动态切换,同样满足需求。
1. 定义模板选择器
public class PropertyCellTemplateSelector : DataTemplateSelector { public DataTemplate BooleanTemplate { get; set; } public DataTemplate MissingPropertyTemplate { get; set; } public DataTemplate DefaultTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { var cell = container as DataGridCell; var propWrapper = cell?.Column.Tag as PropertyWrapper; var elementWrapper = item as ElementWrapper; if (elementWrapper == null || !elementWrapper.HasProperty(propWrapper.PropertyName)) { return MissingPropertyTemplate; } return propWrapper.PropertyType == typeof(bool) ? BooleanTemplate : DefaultTemplate; } }
2. XAML中配置模板与选择器
<Window.Resources> <DataTemplate x:Key="BooleanCellTemplate"> <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Properties[(Tag as PropertyWrapper).PropertyName].Value, Mode=TwoWay}"/> </DataTemplate> <DataTemplate x:Key="MissingPropertyTemplate"> <TextBlock Text="#N/A" HorizontalAlignment="Center" VerticalAlignment="Center"/> </DataTemplate> <DataTemplate x:Key="DefaultCellTemplate"> <TextBlock Text="{Binding Properties[(Tag as PropertyWrapper).PropertyName].Value}"/> </DataTemplate> <local:PropertyCellTemplateSelector x:Key="PropertySelector" BooleanTemplate="{StaticResource BooleanCellTemplate}" MissingPropertyTemplate="{StaticResource MissingPropertyTemplate}" DefaultTemplate="{StaticResource DefaultCellTemplate}"/> </Window.Resources> <DataGrid x:Name="MyDataGrid" ItemsSource="{Binding ElementWrappers}"/>
3. 后台创建列
foreach (var propWrapper in ViewModel.PropertyWrappers) { MyDataGrid.Columns.Add(new DataGridTemplateColumn { Header = propWrapper.DisplayName, CellTemplateSelector = FindResource("PropertySelector") as PropertyCellTemplateSelector, Tag = propWrapper }); }
关键注意事项
- MVVM合规性:两种方案均将UI逻辑限制在View层,ViewModel仅负责提供数据与业务逻辑,完全符合MVVM原则。
- CheckBox事件处理:放弃直接使用
Checked/Unchecked事件,改用双向绑定到PropertyWrapper.Value;如需业务逻辑,可在PropertyWrapper的Value属性变更时触发,或绑定ICommand到CheckBox的Command属性。 - 性能优化:自定义列方案的性能优于模板选择器,尤其在属性数量较多时,无需频繁切换模板实例。
内容的提问来源于stack exchange,提问作者Stephen Chipman
相关产品推荐
相关产品推荐

