Search code examples
wpfmvvmdatacontext

Changing content changes DataContext when using DataTemplate


X problem.

I want to enlarge (let it take whole available space) a part of window content temporarily.

Window layout is pretty complicated: many nested panels, splitters, content to enlarge is 10 levels deep. Changing Visibility to stretch content is simply not enough (thanks to splitters) and seems very complicated.

Y problem

I decide to move that content into a user control and do something like (pseudo-code)

if(IsEnlarged)
{
    oldContent = window.Content; // store
    window.Content = newContent;
}
else
    window.Content = oldContent; // restore

No problems. It was working perfectly in test project ... until I start using data templates.

Problem: if data templates are used then as soon as window.Content = newContent occurs, then that newContent.DataContext is lost and become the same as window.DataContext. This will trigger various binding errors, attached behaviors suddenly changes to default value, etc.. all kind of bad stuff.

Question: why DataContext is changing? How to prevent/fix this issue?


Here is a repro (sorry, can't make it any shorter):

MainWindow.xaml contains

<Window.Resources>
    <DataTemplate DataType="{x:Type local:ViewModel}">
        <local:UserControl1 />
    </DataTemplate>
</Window.Resources>
<Grid Background="Gray">
    <ContentControl Content="{Binding ViewModel}" />
</Grid>

MainWindow.cs contains

public ViewModel ViewModel { get; } = new ViewModel();

public MainWindow()
{
    InitializeComponent();
    DataContext = this;
}

UserControl1.xaml contains

<Button Width="100"
        Height="100"
        CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
        Command="{Binding Command}" />

ViewModel (using DelegateCommand)

public class ViewModel
{
    public DelegateCommand Command { get; set; }

    bool _set;
    object _content;

    public ViewModel()
    {
        Command = new DelegateCommand(o =>
        {
            var button = (Button)o;
            var window = Window.GetWindow(button);
            _set = !_set;
            if (_set)
            {
                _content = window.Content;
                var a = button.DataContext; // a == ViewModel
                window.Content = button;
                var b = button.DataContext; // b == MainWindow ??? why???
            }
            else
                window.Content = _content;
        });
    }
}

Set breakpoint on var a = ..., start program, click the button, do steps and observe button.DataContext as well as binding error in Output window.


Solution

  • ok here some general thoughts.

    if you bind a object(viewmodel) to the Content property of the contentcontrol then wpf uses a DataTemplate to visualize the object. if you dont have a DataTemplate you just see object.ToString(). DataContext inheritance means that the DataContext is inherited to the child elements. so a real usercontrol will inherit the DataContext from the parent. the are common mistakes you find here on stackoverflow when creating UserControls - they often break DataContext inheritance and set the DataContext to self or a new DataContext.

    UserControl DataContext Binding