Search code examples
wpfdata-bindingdatatemplatedatacontext

How to use DataTemplate to set DataContext


I have been told the best approach to setting DataContext for views in MVVM is to use the DataTemplate. So I am trying to have the DataContext of MainWindow set to an instance of MainWindowViewModel by using a DataTemplate.

I have not been able to figure out how.

I have tried putting the ResourceDictionary in various places (in the App.xaml, in the Window.Resources tag...)

I have googled to no avail. Here's what I have... (it doesn't work but, here it is)

App.xaml:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Dictionary1.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:DataTemplateTesting" >

    <DataTemplate DataType="{x:Type local:MainViewModel}">
        <local:MainWindow/>
    </DataTemplate>

</ResourceDictionary>

MainViewModel.cs

namespace DataTemplateTesting
{
    public class MainViewModel
    {
        public MainViewModel() { }
    }
}

The only other thing I did was add a handler to MainWindow for the DataContextChanged event so I could see if it ever fired... it doesn't.

Any ideas how to fix this??

EDIT: Not that there is anything here that can't be generated, but... here's the MainWindow code.

MainWindow.xaml

<Window x:Class="DataTemplateTesting.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataTemplateTesting"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContextChanged="Window_DataContextChanged"        >
    <Grid>

    </Grid>
</Window>

MainWindow.xaml.cs

namespace DataTemplateTesting
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            int i = 10; //This line exists solely to put a debug stop on.
        }
    }
}

Solution

  • <DataTemplate DataType="{x:Type local:MainViewModel}">
        <local:MainWindow/>
    </DataTemplate>
    

    First, this statement does not mean "Create a MainWindow and set its DataContext to MainViewModel". It actually means whenever you see a MainViewModel just put MainWindow in the visual tree.

    Second you cannot add a Window class as a child to another Visual. If you try you will get an exception Window must be the root of the tree. Cannot add Window as a child of Visual.

    The correct way to do it is like this:

    <Window x:Class="DataTemplateTesting.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataTemplateTesting"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ContentControl>
            <ContentControl.Content>
                <local:MainViewModel/>
            </ContentControl>
        </ContentControl>
    </Grid>
    

    Define this in the resource dictionary:

    <DataTemplate DataType="{x:Type local:MainViewModel}">
        <local:SomeUserControl/>
    </DataTemplate>
    

    and create a user control:

    <UserControl x:Class="DataTemplateTesting.SomeUserControl"
             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"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
        <Grid>
            <TextBox/>
        </Grid>
    </UserControl>
    

    This is useful if your content changes dynamically or you are defining ItemTemplate of a view. Otherwise just set the DataContext of Window manually.

    <Window x:Class="DataTemplateTesting.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:DataTemplateTesting"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
    
        <Window.DataContext>
            <local:MainViewModel>
        </Window.DataContext>
    
        <Grid>
    
        </Grid>