How do I create a ViewModel based on the DataContext object of a DataTemplate?
I've been working with MVVM for a while, and this problem (if it is a problem) has me st开发者_StackOverflow社区umped all the time.
I have an ItemsControl bound to a collection in my MainViewModel
ViewModel
public class MainViewModel : ViewModelBase
{
public ObservableCollection<string> Names { get; set; }
}
XAML
<ItemsControl ItemsSource="{Binding Names}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:NameView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The DataContext property of each is of type string (bound directly to the Model), but what if I wanted the DataContext's to be bound to a ViewModel which is based on the property. How would I go about instantiating the ViewModel and feeding it the Model (string).
I hope that makes sense.
Why not do the following?
public ObservableCollection<NameViewModel> Names { get; set; }
This seems a bit odd, but AFAIK there isn't a specific prohibition against one VM knowing about others.
In a case where you use DI for VM resolution, obviously your design would have to be adjusted. For instance, you might create a NamesView
which is a UserControl with a public DependencyProperty
of type IEnumerable<string>
. Then, the NamesView
's ViewModel is bound to this DP...
I think you're asking about data template matching.
If you want binding to select the data template it uses to present an object at runtime, the simplest way is by putting the templates in the resource dictionary and setting their DataType
property, e.g.:
<ItemsControl ItemsSource="{Binding Things}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type Thing1}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type Thing2}">
...
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
Now if your view model, instead of a Names
property, has a Things
property:
public ObservableCollection<object> Things { get; set; }
you can populate the collection with Thing1
and Thing2
objects and the ItemsControl
will present each with the appropriate template.
If you want to choose a different template based on the value of a property, there are a couple of ways of accomplishing this as well. One is to write a DataTemplateSelector, which gives you very fine-grained control over what template gets selected, but requires you to actually code (and test, and document) something.
Another is to use styles to display and hide content based on a trigger. This doesn't actually select different templates per se, but it accomplishes more or less the same thing. Put this in your item template, and it will display one set of content when Name
is "Thing1" and another when Name
is "Thing2".
A very nice thing about this approach is that unlike template selection, this is dynamic: if the value of the Name
property changes at runtime (and your view model implements property-change notification), so will what appears in the view.
<StackPanel>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="Thing1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<!-- content to display when Name = Thing1 goes here -->
</ContentControl>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="Thing2">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<!-- content to display when Name = Thing2 goes here -->
</ContentControl>
</StackPanel>
Finally, if what you're trying to do is actually create different view models in your collection based on the values of the string in the Names
collection, you do that in the view model. You could do this:
public IEnumerable<object> ViewModels
{
get
{
foreach (string name in Names)
{
switch (name)
{
case "Thing1": yield return new Thing1ViewModel(name);
case "Thing2": yield return new Thing2ViewModel(name);
default: throw new InvalidOperationException();
}
}
}
}
If the values in Names
change at runtime, you have a substantially more complex problem: you'll need to implement ViewModels
as an ObservableCollection<object>
property, and you may need to go so far as to handle collection-change events on the Names
collection and update the ViewModels
collection whenever an item in Names
is added, removed, or changed.
精彩评论