DataGrid列IsReadOnly属性按行绑定失败,如何解决?
首先咱们先搞清楚核心矛盾:为什么你的Binding能正常工作,但IsReadOnly绑定失败?
DataGrid的内置列(比如DataGridCheckBoxColumn)不属于可视化树的一部分,它们的默认DataContext是ViewModel(也就是DataGrid的DataContext),而非每行的数据项MyItem。但列的Binding属性是特殊设计的——它不会直接绑定到列的DataContext,而是会自动将这个绑定应用到每个单元格内的控件上,而每个单元格的DataContext恰好是当前行的MyItem,所以Binding="{Binding MyProp.MyValue}"能正常找到属性。
而IsReadOnly是列本身的属性,它的绑定上下文是列的DataContext(ViewModel),所以你尝试的两种方式都走不通:
IsReadOnly="{Binding MyProp.IsReadOnly, Source={StaticResource proxy}}":proxy的Data是ViewModel,会去ViewModel里找MyProp,但MyProp是MyItem的属性,自然找不到。IsReadOnly="{Binding Data.MyProp.IsReadOnly, Source={StaticResource proxy}}":同样是找ViewModel的Data.MyProp,还是不对。
解决方案1:使用DataGridTemplateColumn(最直观)
改用DataGridTemplateColumn,你可以直接在单元格模板里定义控件,并绑定到行数据的属性。因为模板里的控件属于可视化树,能继承行的DataContext:
<DataGridTemplateColumn Header="MyProp" Visibility="{Binding Data.AnotherProp, Source={StaticResource proxy}, Converter={StaticResource BoolToVisibilityConverter}}"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <!-- CheckBox没有IsReadOnly属性,用IsEnabled配合反转转换器实现只读效果 --> <CheckBox IsChecked="{Binding MyProp.MyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding MyProp.IsReadOnly, Converter={StaticResource InverseBooleanConverter}}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn>
这里需要一个反转布尔值的转换器(把IsReadOnly=true转成IsEnabled=false),转换器代码示例:
public class InverseBooleanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value is bool b ? !b : value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value is bool b ? !b : value; } }
如果是TextBox这类自带IsReadOnly属性的控件,直接绑定即可:
<TextBox Text="{Binding MyProp.MyValue}" IsReadOnly="{Binding MyProp.IsReadOnly}" />
解决方案2:使用附加属性(保留内置列)
如果你想继续使用DataGridCheckBoxColumn,可以通过附加属性把IsReadOnly的绑定传递到每个单元格上(单元格属于可视化树,能访问行DataContext):
首先定义附加属性:
public static class DataGridCellHelper { public static readonly DependencyProperty CellIsReadOnlyBindingProperty = DependencyProperty.RegisterAttached( "CellIsReadOnlyBinding", typeof(Binding), typeof(DataGridCellHelper), new PropertyMetadata(null, OnCellIsReadOnlyBindingChanged)); public static Binding GetCellIsReadOnlyBinding(DependencyObject obj) { return (Binding)obj.GetValue(CellIsReadOnlyBindingProperty); } public static void SetCellIsReadOnlyBinding(DependencyObject obj, Binding value) { obj.SetValue(CellIsReadOnlyBindingProperty, value); } private static void OnCellIsReadOnlyBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is DataGridColumn column && e.NewValue is Binding binding) { // 为列的单元格设置样式,把绑定应用到DataGridCell的IsReadOnly属性 column.CellStyle = new Style(typeof(DataGridCell)) { Setters = { new Setter(DataGridCell.IsReadOnlyProperty, binding) } }; } } }
然后在XAML中使用:
<DataGridCheckBoxColumn Header="MyProp" local:DataGridCellHelper.CellIsReadOnlyBinding="{Binding MyProp.IsReadOnly}" Binding="{Binding MyProp.MyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding Data.AnotherProp, Source={StaticResource proxy}, Converter={StaticResource BoolToVisibilityConverter}}" />
这样每个单元格的IsReadOnly属性会绑定到当前行MyItem的MyProp.IsReadOnly,就能实现按行动态只读了。
内容的提问来源于stack exchange,提问作者Gerard

