从 Catel WPF UserControl 中的 ResourceDictionary 中绑定

Posted

技术标签:

【中文标题】从 Catel WPF UserControl 中的 ResourceDictionary 中绑定【英文标题】:Binding from within a ResourceDictionary in a Catel WPF UserControl 【发布时间】:2015-07-18 05:42:06 【问题描述】:

我正在将我们的 WPF 应用程序的一些视图和视图模型转换为 Catel,作为概念验证。

其中一个用户控件在运行时似乎没有正确绑定到视图模型。我想我明白为什么会这样,但想得到一些关于什么是最好的补救措施的反馈。

代码

我有一个简单的视图,它的模型实际上是ObservableCollection

PersonTable.xaml

需要注意的关键事项:我正在使用 CollectionViewSource 来包装 DataGrid 绑定到的主集合。这样我就可以保持网格自动排序。

<catel:UserControl x:Class="MyApp.PersonTable"
             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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
             xmlns:catel="http://catel.codeplex.com"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="200" d:DataContext="DynamicResource DesignTimeViewModel">
    <UserControl.Resources>
        <ResourceDictionary>
            <CollectionViewSource Source="Binding PersonItems" x:Key="PersonItemsSource">
                <CollectionViewSource.SortDescriptions>
                    <scm:SortDescription PropertyName="DOB" Direction="Descending" />
                </CollectionViewSource.SortDescriptions>
            </CollectionViewSource>

            <ui:DesignPersonViewModel x:Key="DesignTimeViewModel" />
        </ResourceDictionary>
    </UserControl.Resources>

    <Grid>
        <DataGrid ItemsSource="Binding Source=StaticResource PersonItemsSource" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="Binding Name, Mode=TwoWay" 
                                    Header="Name" Width="90"
                                    ElementStyle="StaticResource CellRightAlign" />
                <!-- etc..... -->
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</catel:UserControl>

PersonTableViewModel.cs

视图模型在构造函数中接受模型:

using Catel.MVVM;

public class PersonTableViewModel : ViewModelBase

    public PersonTableViewModel(ObservableCollection<Person> personItems)
    
        this.PersonItems = personItems
    

    public ObservableCollection<Person> PersonItems
    
        get  return GetValue<ObservableCollection<Person>>(PersonItemsProperty); 
        set  SetValue(PersonItemsProperty, value); 
    

    public static PropertyData PersonItemsProperty = 
        RegisterProperty("PersonItems", typeof(ObservableCollection<Person>), () => new ObservableCollection<PersonItems>());

问题

在运行时,网格中不会填充任何项目。尽管在设计时,设计视图模型确实正确地填充了设计视图中的网格。

我对问题的根源是否正确?我相信绑定到PersonItems 属性的控件不是可视化树的一部分,而是嵌入在控件中 -级别资源字典?根据我对文档的阅读,特别是文章 UserControl - Under the hood,似乎 Catel UserControl 类仅将视图模型作为隐藏的内部 DataContext 注入到可视化树中,但我的 Binding 在资源字典中物品可能会被冷落。

假设我是对的,最好的补救措施是什么?

如果我对上述内容的看法是正确的,那么我可以想到一些可能的补救措施,但似乎都不是完美的。我很想知道什么是公认的最佳实践来纠正这种情况。

CollectionViewSource移动到后面的代码;将其公开为依赖属性。我不喜欢这个选项,因为我不能在 XAML 中配置它。 将CollectionViewSource 移动到视图模型。我真的不喜欢这个;将 WPF 组件放在视图模型中会破坏 MVVM。

CollectionViewSource 绑定到原始DataContext(即模型)。那里的问题是设计时视图模型将无法正确绑定。

<CollectionViewSource Source="Binding" ..... >

从绑定到视图模型的代码隐藏中公开一个依赖属性。 更新:这在运行时有效,但现在在设计时失败(因为网格不包含测试数据。)

---- PersonTable.xaml.cs ----

[ViewToViewModel(MappingType = ViewToViewModelMappingType.ViewModelToView]
public ObservableCollection<PersonItem> PersonItems  get  ...  

---- PersonTable.xaml ----

<CollectionViewSource Source="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type PersonTable, Path=PersonItems" ...... >

【问题讨论】:

【参考方案1】:

你的假设都是正确的。但是还有第四个补救措施。将 Resources 放在 Grid 中,这样您就可以在 ViewModel 数据上下文中:

<catel:UserControl x:Class="MyApp.PersonTable"
             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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
             xmlns:catel="http://catel.codeplex.com"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="200" d:DataContext="DynamicResource DesignTimeViewModel">

    <Grid>
        <Grid.Resources>
            <ResourceDictionary>
                <CollectionViewSource Source="Binding PersonItems" x:Key="PersonItemsSource">
                    <CollectionViewSource.SortDescriptions>
                        <scm:SortDescription PropertyName="DOB" Direction="Descending" />
                    </CollectionViewSource.SortDescriptions>
                </CollectionViewSource>

                <ui:DesignPersonViewModel x:Key="DesignTimeViewModel" />
            </ResourceDictionary>
        </Grid.Resources>

        <DataGrid ItemsSource="Binding Source=StaticResource PersonItemsSource" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="Binding Name, Mode=TwoWay" 
                                    Header="Name" Width="90"
                                    ElementStyle="StaticResource CellRightAlign" />
                <!-- etc..... -->
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</catel:UserControl>

【讨论】:

啊!我缺乏使用 WPF 的经验;从未意识到您可以将ResourceDictionary 放在其中一个后代控件中。但是你当然可以……这在运行时和设计时也有效。

以上是关于从 Catel WPF UserControl 中的 ResourceDictionary 中绑定的主要内容,如果未能解决你的问题,请参考以下文章

Catel WPF 延迟验证

如何从 UserControl 访问 WPF 页面中的公共变量?

使用 Catel 创建多个 WPF 应用程序

使用 WPF 在 Catel 中设置 DataContext

WPF 从后面的代码添加的 UserControl 绑定到祖先

UserControl在wpf中使用父元素?