I am converting some of the views and view models of our WPF application over to Catel, as a proof-of-concept.
One of the user controls doesn't seem to be correctly binding to the view model at runtime. I think I understand why that is, but would like to get some feedback on what the best remedy is.
The code
I have a simple view whose model is actually an ObservableCollection
:
PersonTable.xaml
Key things to note: I'm using a CollectionViewSource
that wraps the main collection that the DataGrid
binds to. This is so I can keep the grid auto-sorted.
<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
The view model accepts the model in the constructor:
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>());
}
The Problem
At runtime, no items are populated in the grid. Although at design time, the design view model does correctly populate the grid in the design view.
Am I right about the source of the problem? I believe it's that the control that is bound to the PersonItems
property is not part of the visual tree, but is embedded in a control-level resource dictionary? Based on my reading of the documentation, specifically the article UserControl - Under the hood, it seems that the Catel UserControl
class injects the view model as a hidden inner DataContext
inside the visual tree only, but my {Binding}
inside a resource dictionary item might get left out in the cold.
Assuming I'm right, what's the best remedy?
If I'm right about the above, then I can think of a few possible remedies, none of which seem perfect. I would love to know what the accepted best practice is to remedy this situation.
- Move the
CollectionViewSource
to the code behind; expose it as a dependency property. I don't love this option because I can't then configure it in XAML. - Move the
CollectionViewSource
to the view model. I really don't love this one; putting WPF components in the view model breaks MVVM. Bind the
CollectionViewSource
to the originalDataContext
(i.e. the model). The problem there is that then the design-time view model would not bind correctly.<CollectionViewSource Source="{Binding}" ..... >
Expose a dependency property from the code-behind that is bound to the view model. UPDATE: this works at runtime, but now fails at design time (in that the grid does not contain the test data.)
---- 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}" ...... >