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
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.
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;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.