简体   繁体   中英

DataContext not set when using View as DataTemplate in ItemsControl

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM