WPF 自定义 DatagridColumn 绑定问题

Posted

技术标签:

【中文标题】WPF 自定义 DatagridColumn 绑定问题【英文标题】:WPF Custom DatagridColumn Binding issue 【发布时间】:2017-02-07 13:10:12 【问题描述】:

我正在尝试为我可以在我的应用程序中重复使用的数据网格定义一个新的列模板,但是当我尝试使用它时,我得到:

System.Windows.Data 错误:2:找不到管理 FrameworkElement 或 FrameworkContentElement 为目标元素。 绑定表达式:路径=CanLogin;数据项=空;目标元素是 'DataGridBetterCheckBoxColumn' (HashCode=56040243);目标属性是 “isChecked”(类型“对象”)

列的 XAML:

<DataGridTemplateColumn x:Class="BACSFileGenerator.UserControls.DataGridBetterCheckBoxColumn"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:BACSFileGenerator.UserControls"
             mc:Ignorable="d" 
             x:Name="ColumnRoot"
             >
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox IsChecked="Binding isChecked, Source=x:Reference Name=ColumnRoot"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

代码背后:

using System.Windows;
using System.Windows.Controls;

namespace BACSFileGenerator.UserControls


    public partial class DataGridBetterCheckBoxColumn : DataGridTemplateColumn
    

        public object isChecked
        
            get  return (object)GetValue(isCheckedProperty); 
            set  SetValue(isCheckedProperty, value); 
        

        public static readonly DependencyProperty isCheckedProperty =
            DependencyProperty.Register("isChecked", typeof(object),
              typeof(DataGridBetterCheckBoxColumn), new PropertyMetadata(null));

        public DataGridBetterCheckBoxColumn()
        
            InitializeComponent();
        
    

然后我尝试像这样使用它:

<DataGrid Margin="0,0,0,10" ItemsSource="Binding UserAccessGrid" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="User" Binding="Binding User" IsReadOnly="True"/>
                <uc:DataGridBetterCheckBoxColumn Header="Login" isChecked="Binding CanLogin"/>
                <uc:DataGridBetterCheckBoxColumn Header="Export Payments" isChecked="Binding canExportPayments"/>
                <uc:DataGridBetterCheckBoxColumn Header="Create File Layouts" isChecked="Binding canCreateFileLayouts"/>
                <uc:DataGridBetterCheckBoxColumn Header="Change User Access" isChecked="Binding canChangeUserAccess"/>
            </DataGrid.Columns>
</DataGrid>

谁能向我解释一下正确的做法?

【问题讨论】:

【参考方案1】:

假设我们有

public class ViewModel

   public bool CanBeUsed get;set;
   public List<Employee> Employeesget;set;

可能让您感到困惑的几点:

    一个属性只会有一个DataGridBetterCheckBoxColumn 实例化。多个记录并不意味着属性的多个列实例。而是为每个DataGridColumn 创建多个DataGridCell

    但是

    DataGridColumn 不是FrameworkElementVisual,所以它不会出现在VisualTree 中,因为它不是FrameworkElement,所以它没有DataContext 属性。没有DataContext 你的Binding 将如何工作?问你自己。由于此Column 不能设置其DataContext,因此它必须具有ElementNameSourceRelativeSource 才能使其Binding 工作。

    现在,我们知道DataGridColumn 将只有一个实例,因此它的Binding 应该(被制作为)使用DataGridDataContext(集合属性将是其中的一部分)。

    现在,看看你的Binding,它的Source / RelativeSource 在哪里?没有。现在,RelativeSource 在这里有意义吗?由于DataGridColumn 没有出现在VisualTree 中,所以RelativeSource 不会在这里应用。我们留下了Source 财产。我们现在应该为 Source 设置什么?输入DataContext Inheritance

    DataContext 继承

    DataContext 继承仅适用于通过VisualTree 连接的FrameworkElement。因此,我们需要一种机制,通过它我们可以将这个DataContext 降低到我们的DataGridColumn。 输入Binding Proxy

    public class BindingProxy : Freezable
    
        #region Overrides of Freezable
    
        protected override Freezable CreateInstanceCore()
        
             return new BindingProxy();
        
    
        #endregion
    
        public object Data
        
           get  return (object)GetValue(DataProperty); 
           set  SetValue(DataProperty, value); 
        
    
        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
     
    

    如果我们将这个BindingProxy 的一个实例声明为Resource,我们可以得到我们的Source

    <DataGrid Margin="0,52,0,10" ItemsSource="Binding Records" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False">
       <DataGrid.Resources>
         <uc:BindingProxy x:Key="FE" Data="Binding"/>
       </DataGrid.Resources>
       <DataGrid.Columns>
          <DataGridTextColumn x:Name="dgt" Header="User" Binding="Binding User" IsReadOnly="True"/>
          <uc:DataGridBetterCheckBoxColumn isChecked="Binding Data.CanBeUsed, Source=StaticResource FE" Header="CanLogin"/>
       </DataGrid.Columns>
    </DataGrid>
    

    现在,你会看到你讨厌的Binding Error 不见了。

    要使您的CheckBox 绑定正常工作,您需要处理其Loaded 事件。

      <DataGridTemplateColumn.CellTemplate>
         <DataTemplate>
           <CheckBox Loaded="CheckBox_Loaded"/>
         </DataTemplate>      
      </DataGridTemplateColumn.CellTemplate>
    

    代码:

      void CheckBox_Loaded(object sender, RoutedEventArgs e)
      
          Binding b = new Binding();
          b.Path = new PropertyPath("isChecked");
          b.Mode = BindingMode.TwoWay;
          b.Source = this;
    
          CheckBox cb = sender as CheckBox;
    
          BindingOperations.SetBinding(cb , CheckBox.IsCheckedProperty, b);
      
    

    但是现在,我们这里有一个逻辑问题。我们所有的CheckBox 现在都绑定到DataContext 属性CanBeUsed,这将保持不变。您可能会认为CanBeUsed 应该是Employee 的属性,它是ItemsSource 而不是DataGridDataContext。因此,当您选中/取消选中任何CheckBox 时,都会做出相同的响应。

但是,我们希望将我们的isChecked 属性绑定到Employee 记录的某个属性,该记录对于每个DataGridRow 都将保持差异。因此,我们现在需要更改isChecked 的定义,之后整个代码将如下所示:

public partial class DataGridBetterCheckBoxColumn : DataGridTemplateColumn

    public BindingBase isChecked  get; set; 

    public DataGridBetterCheckBoxColumn()
    
        InitializeComponent();
    

    void CheckBox_Loaded(object sender, RoutedEventArgs e)
              
        CheckBox cb = sender as CheckBox;

        BindingOperations.SetBinding(cb , CheckBox.IsCheckedProperty, isChecked);
    
 

用法:

<uc:DataGridBetterCheckBoxColumn isChecked="Binding CanLogin, Mode=TwoWay" Header="CanLogin"/>

如果我遗漏了任何一点,请告诉我。

【讨论】:

以上是关于WPF 自定义 DatagridColumn 绑定问题的主要内容,如果未能解决你的问题,请参考以下文章

仅当对象属性为 true 时才绑定 DataGridColumn

WPF - 如何绑定 DataGridTemplateColumn

标头为“*”的 DataGridColumn 已存在于 DataGrid 的 Columns 集合中

wpf 自定义控件的自定义属性的数据绑定问题

如何绑定 DataGridColumn.Visibility?

自定义 WPF 数据绑定:如何添加自定义逻辑?