Привязка из ResourceDictionary в Catel WPF UserControl

Я конвертирую некоторые представления и модели представлений нашего приложения 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 капот, кажется, что класс 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}" ...... >
    

person Zach Blocker    schedule 18.07.2015    source источник


Ответы (1)


Все ваши предположения верны. Но есть четвертое средство. Поместите ресурсы в сетку, чтобы вы находились внутри контекста данных 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>
person Geert van Horrik    schedule 18.07.2015
comment
Ах! Мне не хватает опыта работы с WPF; никогда не понимал, что вы можете поместить ResourceDictionary в один из дочерних элементов управления. Но, конечно, вы можете... Это работает и во время выполнения, и во время разработки. - person Zach Blocker; 18.07.2015