【问题标题】:DataGridColumnHeader of a DataGrid defined in a DataTemplate for a ViewModel在 ViewModel 的 DataTemplate 中定义的 DataGrid 的 DataGridColumnHeader
【发布时间】:2014-02-27 14:14:13
【问题描述】:

我已成功使用 ProxyElement 将我的数据网格的数据上下文传递给 DataGridColumnHeaders。但是,我正在尝试一些新的东西,我只是不知道我在这里做错了什么。

这是我想要做的:我正在创建一个 UserControl 并将它与我的资源文件中的 ViewModel 相关联(请参阅下面的 Resources.xaml 代码 sn-p)。

Resources.xaml:

<ResourceDictionary
    xmlns:myVm="clr-namespace:..."
    xmlns:myUserControl="clr-namespace:...">
    <DataTemplate DataType={x:Type myVm:DummyModel}">
        <myUserControl:DummyUserControl />
    </DataTemplate>
</ResourceDictionary>

现在在我的用户控件中,我有一个带有 DataGridComboBoxColumn 的 DataGrid。我正在尝试访问我的数据上下文以设置其项目源,过去我可以使用代理元素来做到这一点。然而这一次我不能这样做。 (参见下面的 DummyUserControl.xaml 代码 sn-p)

DummyUserControl.xaml:

<UserControl x:Class="Client.MyControl.DummyUserControl"
    ...>
    <UserControl.Resources>
        <FrameworkElement x:Key="ProxyElement" x:Name="ProxyElement" 
            DataContext="{Binding}" />
    </UserControl.Resources>

    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
        <DataGridComboBoxColumn
            Header="Company"
            ItemsSource="{Binding Path=DataContext.ProductCompanies, 
                Source={StaticResource ProxyElement}}"
            DisplayMemberPath="Name" SelectedValuePath="Id"
            SelectedValueBinding="{Binding CompanyId}" />
    </DataGrid>
</UserControl>

当我这样做时,我的绑定失败并显示以下消息:

System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
    BindingExpression:(no path); DataItem=null; target element is 'FrameworkElement'
    (Name='ProxyElement'); target property is 'DataContext' (type 'Object')

我不知道在这里做什么。我记得读过数据模板的数据上下文是自动设置的,所以我不知道为什么在这种情况下数据上下文为空。为了证明它为空,我还尝试在代码隐藏文件中设置绑定并添加断点来检查其值(为空)。

谁能建议在这里做什么?

编辑 1

我也尝试了以下方法:

  • 完全删除 ProxyElement 并查看它是否可以检测到 DataContext。不出所料,这失败了。

  • 尝试绑定到模板化父级。失败。

  • 尝试绑定到 UserControl 本身。失败。

  • 我还尝试引用要显示此视图模型的父项的数据上下文,它位于 TabControl 的 TabItem 中。

所有备用绑定都给了我与上述错误相同的错误。

【问题讨论】:

    标签: wpf xaml datagrid datatemplate


    【解决方案1】:

    这是解决此问题的(可行但不是首选)解决方案。到最后你就会明白为什么它不是首选。

    这个问题的关键是理解数据模板的数据上下文是什么以及如何工作。每当您为视图模型定义数据模板时,随后的视图的数据上下文,无论是用户控件还是 xaml 本身,都是视图模型!这对任何人来说都不足为奇。

    但这会让人大吃一惊:如果你指定了一个用户控件,那么用户控件的数据上下文在用户控件的构建过程中是没有设置的!换句话说,在用户控件的构造函数中,数据上下文将为空。此外,任何在构造时依赖于数据上下文的 XAML 代码,在本例中是我的名为 ProxyElement 的 FrameworkElement 资源,因为它是在用户控件的构造时构造的,所以它的 DataContext 设置为 null!

    那么 DataContext 什么时候设置?只需在创建用户控件之后。在伪代码中,以下描述了绘制 ViewModel 背后的逻辑:

    1. 绘制 ViewModel x;
    2. ResourceDictionary 中的 DataTemplate 表示 ViewModel x 可以使用 UserControl abc 进行绘制
    3. 让我们创建一个新的 UserControl abc 实例
    4. 现在让我们将 abc 的 DataContext 分配给 ViewModel 本身。
    5. 让我们返回新创建的 UserControl abc 实例

    那么我们如何解决这个问题呢?

    UserControl.xaml:

    <UserControl ...
        DataContextChanged="DaCoHasChanged">
        <UserControl.Resources>
            <FrameworkElement x:Key="ProxyElement" /> <!--Remove DataContext="{Binding}"-->
        </UserControl.Resources>
        <DataGrid ...>
            <DataGridComboBoxColumn
                ItemsSource="{Binding Path=DataContext.ProductCompanies, 
                    Source={StaticSource ProxyElement}}"
                ... />
        </DataGrid>
    </UserControl>
    

    UserControl.xaml.cs:

    private void DaCoHasChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var proxyElement = Resources["ProxyElement"] as FrameworkElement;
        proxyElement.DataContext = e.NewValue; // instead of e.NewValue, you could 
                                               // also say this.DataContext
    }
    

    我正在尝试找出一种摆脱代码隐藏文件中代码的方法。但在那之前,如果其他人遇到了这个问题,那么他们也许可以从这个解决方案中得到启发。

    此解决方案背后的概念归功于:How to set the DataContext for a View created in DataTemplate from ViewModel

    【讨论】:

      【解决方案2】:

      试试这个

      <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
          <DataGridTemplateColumn Header="Company">
             <DataGridTemplateColumn.CellTemplate>
              <DataTemplate>
                <ComboBox 
              ItemsSource="{Binding Path="{Binding DataContext.ProductCompanies,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
              DisplayMemberPath="Name" SelectedValuePath="Id"
              SelectedValueBinding="{Binding CompanyId}" />
        </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        </DataGrid.Columns>
        </DataGrid>
      

      现在,当我遇到您的问题时,我认为问题出在 DataGridComboBoxColumn 我不知道为什么它不使用 RelativeResource 绑定。用 DataGridTemplateColumn 试试,你不需要任何 ProxyElement 我希望这会有所帮助。

      【讨论】:

      • 我应该如何更改我的 ProxyElement 的声明?
      • 哦,是的,您在其中设置了源。但我认为没有必要在 Binding 中提供 Source,让 Combobox 直接从上面获取 DataContext。我更新了答案
      • 它没有用。这是我得到的错误:System.Windows.Data 错误:2:找不到目标元素的管理 FrameworkElement 或 FrameworkContentElement。绑定表达式:路径=产品公司;数据项=空;目标元素是“DataGridComboBoxColumn”(HashCode=22979266);目标属性是“ItemsSource”(类型“IEnumerable”)
      • 您的意思是从绑定中删除源后出现错误?
      • 是的。我实际上尝试了您建议的解决方案。我已经在 Edit 1 下列出了它,但不知何故,我的所有代码 sn-ps 都从我的编辑中删除了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-17
      • 1970-01-01
      • 2015-03-25
      • 1970-01-01
      • 2019-02-18
      • 2012-07-07
      • 1970-01-01
      相关资源
      最近更新 更多