问题
I have an ObservableCollection of ViewModels that I'd like to bind to an ItemsControl containing the associated child Views. When I add ViewModels to my collection, an appropriate number of child Views are generated in the ItemsControl. However, the DataContext for each of the generated views is null. If I inline my child view, it works correctly. So, what do I need to do to set the DataContext for my child views to my ViewModels?
Here's the relavent bits in my parent ViewModel:
public ObservableCollection<ChildViewModel> Numbers { get; set; }
public ParentViewModel()
{
Numbers = new ObservableCollection<ChildViewModel>();
}
private void ShowNumbers()
{
foreach (var num in Enumerable.Range(0, number))
{
var childView = new ChildViewModel(number.ToString());
Numbers.Add(childView);
}
}
Relevant bit from the Parent View:
<ItemsControl ItemsSource="{Binding Numbers, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ChildViewModel}">
<v:ChildView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The Child View:
<UserControl x:Class="TestWpfApp.Views.ChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<Label Content="{Binding NumberString}" Width="30" Height="30" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center"/>
</Grid>
</UserControl>
The Child ViewModel:
public class ChildViewModel : BindableBase
{
private string numberString;
public string NumberString
{
get
{
return numberString;
}
set
{
SetProperty(ref numberString, value);
}
}
public ChildViewModel() { }
public ChildViewModel(string number)
{
NumberString = number;
}
}
Obviously, I have something misconfigured, but I can't for the life of me figure out what.
FYI I am using the Prism Library
回答1:
WPF automatically sets the DataContext
of an item container element in an ItemsControl
to the appropriate item instance, so that it can be inherited into the ItemTemplate
. Apparently this mechanism is disabled when you set the prism:ViewModelLocator.AutoWireViewModel
property.
So, just remove it from your ChildView's XAML:
<UserControl x:Class="TestWpfApp.Views.ChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/">
<Grid>
<Label Content="{Binding NumberString}" Width="30" Height="30"
BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center" />
</Grid>
</UserControl>
As a general rule, a UserControl should never explicitly set its own DataContext
, neither directly nor by a mechanism like AutoWireViewModel
, because that effectively prevents inheriting a DataContext
from its parent control.
回答2:
An appropriate number of child Views are generated in the ItemsControl.
So it can be concluded that DataContext
is set to your viewModel
correctly, but DataTemplate's
is not applied.
I have the same problem and I am also using Prism
library. I would like to share about the way how it can be done. Maybe it helps to you. I've used CompositeCollection
:
ViewModel:
public class MainWindowVM:ViewModelBase
{
public MainWindowVM()
{
LoadData();
}
private void LoadData()
{
ObservableCollection<Human> coll = new ObservableCollection<Human>();
for (int indexLoop = 0; indexLoop < 5; indexLoop++)
{
if (indexLoop % 2 == 0)
{
coll.Add(new Sportsman() {FirstName=indexLoop.ToString(), LastName=indexLoop.ToString()});
}
else
{
coll.Add(new Employee() { FirstName = indexLoop.ToString(), LastName = indexLoop.ToString()});
}
}
CompositeCollection compositeColl = new CompositeCollection();
compositeColl.Add(new CollectionContainer() { Collection = coll });
FooData = compositeColl;
}
private CompositeCollection fooData;
public CompositeCollection FooData
{
get { return fooData; }
set
{
fooData = value;
}
}
}
Model:
public class Human
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
public class Sportsman:Human
{ }
public class Employee:Human
{ }
View:
<Window x:Class="ItemsControlWithDataTemplates.MainWindow"
<!--The code is omitted for the brevity-->
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowVM/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding FooData}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type model:Employee}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text=" ( Employee"/>
<TextBlock Text="{Binding LastName}"/>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type model:Sportsman}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text=" - Sportsman "/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type model:Human}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Grid>
</Window>
Actually I set DataContext
in the following way in my Prism
application:
public MyUserControl(IMyViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
来源:https://stackoverflow.com/questions/37453162/datacontext-not-set-when-using-view-as-datatemplate-in-itemscontrol