基于WPF Binding&Reflection的动态DataGrid行详情属性编辑问询
Hey there, let's walk through how to get dynamic RowDetails editing working for your auto-generated DataGrid. Since your rows are all derived from a base class that exposes a list of editable properties (via reflection), we need the RowDetails to adapt to that dynamic list instead of hardcoding controls. Here's how to pull it off:
1. 确认基类与数据模型结构
首先,确保你的基类(比如命名为BaseRecord)能正确返回当前记录需要编辑的属性列表。这里用反射筛选属性,你可以根据业务需求调整过滤逻辑:
public abstract class BaseRecord { // 返回需要显示/编辑的属性集合 public List<PropertyInfo> EditableProperties => GetType().GetProperties() .Where(p => !p.GetGetMethod().IsStatic && p.CanWrite) // 排除静态、只读属性 .ToList(); } // 示例子类:实际业务中可以有多个不同的子类 public class CustomerRecord : BaseRecord { public string FullName { get; set; } public int Age { get; set; } public DateTime RegistrationDate { get; set; } public bool IsActive { get; set; } }
2. 动态生成RowDetails编辑控件
因为属性是动态的,我们有两种常用方案来生成对应的编辑控件,按需选择即可:
方案A:XAML模板驱动(适合轻量场景)
在DataGrid的XAML中定义RowDetailsTemplate,用ItemsControl遍历基类的EditableProperties,通过数据触发器根据属性类型自动匹配编辑控件:
<DataGrid x:Name="DynamicDataGrid" AutoGenerateColumns="True" ItemsSource="{Binding RecordList}" LoadingRowDetails="DynamicDataGrid_LoadingRowDetails"> <DataGrid.RowDetailsTemplate> <DataTemplate> <StackPanel Margin="12" Orientation="Vertical"> <TextBlock FontWeight="SemiBold" FontSize="14" Text="编辑记录"/> <!-- 遍历可编辑属性,动态生成编辑项 --> <ItemsControl ItemsSource="{Binding EditableProperties}" Margin="0,8,0,0"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="0,4,0,0"> <TextBlock Text="{Binding Name}" Width="120" VerticalAlignment="Center"/> <!-- 根据属性类型切换编辑控件 --> <ContentControl> <ContentControl.Style> <Style TargetType="ContentControl"> <!-- 字符串类型用TextBox --> <Style.Triggers> <DataTrigger Binding="{Binding PropertyType}" Value="{x:Type sys:String}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <TextBox Width="220" Text="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGridRow}, Path={Binding Name}, Mode=TwoWay}"/> </DataTemplate> </Setter.Value> </Setter> </DataTrigger> <!-- 整数类型用带数字验证的TextBox --> <DataTrigger Binding="{Binding PropertyType}" Value="{x:Type sys:Int32}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <TextBox Width="220" Text="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGridRow}, Path={Binding Name}, Mode=TwoWay}" PreviewTextInput="NumericOnly_PreviewTextInput"/> </DataTemplate> </Setter.Value> </Setter> </DataTrigger> <!-- 日期类型用DatePicker --> <DataTrigger Binding="{Binding PropertyType}" Value="{x:Type sys:DateTime}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <DatePicker Width="220" SelectedDate="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGridRow}, Path={Binding Name}, Mode=TwoWay}"/> </DataTemplate> </Setter.Value> </Setter> </DataTrigger> <!-- 布尔类型用CheckBox --> <DataTrigger Binding="{Binding PropertyType}" Value="{x:Type sys:Boolean}"> <Setter Property="Content"> <Setter.Value> <CheckBox IsChecked="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGridRow}, Path={Binding Name}, Mode=TwoWay}"/> </Setter.Value> </Setter> </DataTrigger> </Style.Triggers> </Style> </ContentControl.Style> </ContentControl> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <Button Content="保存修改" Margin="0,12,0,0" Width="100" Click="SaveRowChanges_Click"/> </StackPanel> </DataTemplate> </DataGrid.RowDetailsTemplate> </DataGrid>
注意:需要在XAML头部引入系统类型命名空间:xmlns:sys="clr-namespace:System;assembly=mscorlib"
方案B:后台代码生成(适合复杂场景)
如果需要更精细的控制(比如根据自定义Attribute设置验证规则、控件样式),可以在RowDetails加载时动态生成控件:
private void DynamicDataGrid_LoadingRowDetails(object sender, DataGridRowDetailsEventArgs e) { var currentRecord = e.Row.DataContext as BaseRecord; if (currentRecord == null) return; // 获取RowDetails里的容器控件 var container = e.DetailsElement.FindName("EditContainer") as StackPanel; if (container == null) return; container.Children.Clear(); // 遍历属性生成编辑控件 foreach (var prop in currentRecord.EditableProperties) { // 添加属性标签 var label = new TextBlock { Text = prop.Name, Width = 120, Margin = new Thickness(0,4,0,0), VerticalAlignment = VerticalAlignment.Center }; container.Children.Add(label); // 根据属性类型创建对应编辑控件 FrameworkElement editControl = null; var binding = new Binding(prop.Name) { Mode = BindingMode.TwoWay, Source = currentRecord }; switch (Type.GetTypeCode(prop.PropertyType)) { case TypeCode.String: editControl = new TextBox { Width = 220 }; BindingOperations.SetBinding(editControl, TextBox.TextProperty, binding); break; case TypeCode.Int32: editControl = new TextBox { Width = 220 }; BindingOperations.SetBinding(editControl, TextBox.TextProperty, binding); ((TextBox)editControl).PreviewTextInput += NumericOnly_PreviewTextInput; break; case TypeCode.DateTime: editControl = new DatePicker { Width = 220 }; BindingOperations.SetBinding(editControl, DatePicker.SelectedDateProperty, binding); break; case TypeCode.Boolean: editControl = new CheckBox { VerticalAlignment = VerticalAlignment.Center }; BindingOperations.SetBinding(editControl, CheckBox.IsCheckedProperty, binding); break; // 可扩展其他类型,比如枚举用ComboBox } if (editControl != null) { container.Children.Add(editControl); } } // 添加保存按钮 var saveBtn = new Button { Content = "保存修改", Margin = new Thickness(0,12,0,0), Width = 100 }; saveBtn.Click += (s, args) => SaveRecordChanges(currentRecord); container.Children.Add(saveBtn); } private void SaveRecordChanges(BaseRecord record) { // 这里写保存逻辑:比如提交到数据库、更新数据源等 // 因为用了双向绑定,record的属性已经是最新值了 MessageBox.Show($"已保存{record.GetType().Name}的修改"); } private void NumericOnly_PreviewTextInput(object sender, TextCompositionEventArgs e) { e.Handled = !char.IsDigit(e.Text, 0); }
对应的XAML需要给容器命名:
<DataGrid.RowDetailsTemplate> <DataTemplate> <StackPanel x:Name="EditContainer" Margin="12" Orientation="Vertical"> <TextBlock FontWeight="SemiBold" FontSize="14" Text="编辑记录"/> </StackPanel> </DataTemplate> </DataGrid.RowDetailsTemplate>
3. 关键注意事项
- 属性筛选逻辑:基类的
EditableProperties一定要做好过滤,排除不需要编辑的属性(比如主键、计算属性),避免生成多余控件。 - 绑定路径正确性:不管用XAML还是后台绑定,都要确保绑定路径指向属性的
Name,这样才能正确关联到当前记录的属性值。 - 类型覆盖:根据你的业务数据类型扩展控件匹配逻辑,比如枚举类型可以绑定到ComboBox,浮点数可以用带小数点验证的TextBox。
- 验证与错误提示:如果需要输入验证,可以在绑定中添加
ValidationRules,或者给控件设置ToolTip显示错误信息。
内容的提问来源于stack exchange,提问作者Joe M. Doe

