关于mvvm:从Catel WPF UserControl中的ResourceDictionary内部进行绑定

Binding from within a ResourceDictionary in a Catel WPF UserControl

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

用户控件之一似乎在运行时未正确绑定到视图模型。我想我理解这是为什么,但是想获得关于最佳补救方法的一些反馈。

代码

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

PersonTable.xaml

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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-在引擎盖下,Catel UserControl类似乎仅将视图模型作为隐藏的内部DataContext注入到可视树内,而将我的{Binding}注入到可视树内资源字典项可能会被冷落。

假设我是对的,最好的补救方法是什么?

如果我对以上内容是正确的,那么我可以想到一些可能的补救措施,但似乎没有一个是完美的。我很想知道可以接受的最佳实践来纠正这种情况。

  • CollectionViewSource移到后面的代码中;将其公开为依赖项属性。我不喜欢此选项,因为之后无法在XAML中对其进行配置。
  • CollectionViewSource移动到视图模型。我真的不喜欢这个。将WPF组件放在视图模型中会破坏MVVM。
  • CollectionViewSource绑定到原始的DataContext(即模型)。问题在于设计时视图模型将无法正确绑定。

    1
    <CollectionViewSource Source="{Binding}" ..... >
  • 从绑定到视图模型的隐藏代码中公开依赖项属性。更新:这在运行时有效,但是现在在设计时失败(因为网格不包含测试数据。)

    1
    2
    3
    4
    5
    6
    7
    8
    ---- 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}" ...... >

您的假设都是正确的。 但是有第四种补救方法。 将资源放入网格内,以便您位于ViewModel数据上下文内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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>