2

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.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • also add UserControl1 code where u are setting its DC, and why and where DataTemplate is used ? – AnjumSKhan Feb 25 '16 at 14:23
  • @AnjumSKhan, no need to set `UserControl1.DataContext`, because of data templating. Everything is ok until you click the button. And it's a repro to demonstrate the case, not real project (where datatemplates are must). – Sinatr Feb 25 '16 at 14:24
  • DataTemplate is used in MainWindow, so naturally it will get its DC from MainWindow ofcourse. If you set UC's DC in UC code, only then it will be different. In your UC, there won't be any DC because you are not setting it. Once it becomes part of MainWindow, it will get its DC. – AnjumSKhan Feb 25 '16 at 14:26
  • @AnjumSKhan, then tell me why `a == ViewModel`? Can you please try code before asking questions? – Sinatr Feb 25 '16 at 14:27
  • hopefully you dont have any controls in your real viewmodels - its a no go. to see what happens just use Snoop to check your DataContext at runtime – blindmeis Feb 25 '16 at 14:28
  • @blindmeis, tell me please how Snoop can help? I was able to localize the problem to `window.Content = button` line already. Can you check it for me? My *best* idea so far is to set `button.DataContext` immediately after setting `window.Content`, while providing some mechanism for all attached behaviors to ignore content *switching* (and it sounds like a pain). – Sinatr Feb 25 '16 at 14:31
  • the whole viewmodel code you have post has nothing todo with MVVM, so its hard to get what you really want. – blindmeis Feb 25 '16 at 14:36
  • @blindmeis, sigh, because it's repro. I will have pure MVVM using attached behaviors at the end. I want to fix the problem with `DataContext` being changed when using data templates. Without data templates everything works (so it must be something related to data templates). I don't know how to make better mcve to demonstrate it. – Sinatr Feb 25 '16 at 14:36
  • 1
    a mvvm repro with controls insight the viewmodel its not a mvvm repro sorry. – blindmeis Feb 25 '16 at 14:38
  • @blindmeis, I agree absolutely, repro is not MVVM as it is. I thought someone would try the code and play with it to find out the reason why `button.DataContext` changes. I could use `ListBox` in the view containing many `ViewModel`'s, does that make it better to undestand why data templating? And I want some item of `ListBox` to take whole area of window and then back. – Sinatr Feb 25 '16 at 14:41
  • When I do something like this, the cheap and dirty way to accomplish it is to have two copies of the UI in the same window, both of which are inside of a Grid. They are both bound to the same DataContext, but only the visible sub UI is seen. You can toggle between which is visible and which is hidden depending on your requirements. Usually I do that just for loading screens (the overlayed UI nicely disables and hides the underlaying UI) –  Feb 25 '16 at 14:47
  • DataTemplates and ContentControls are probably a better solution, tho :/ –  Feb 25 '16 at 14:48
  • @Will, nice idea, thanks. In fact I was thinking to make some kind of *universal enlrager*, which will work with any `FrameworkElement`. It would be the best if it would work. – Sinatr Feb 25 '16 at 14:50

2 Answers2

1

You must be trying to use your DataTemplate as ContentTemplate for your ContentControl. As ContentTemplate operates on Content, so it will use Content as its DataContext. And your Content contains ViewModel.

Once your Button is no longer part of your DataTemplate, so it will use MainWindow's DataContext.

No seeing your comments, I am assuming that you want DataContext of UserControl to remain intact, even if your UserControl is not part of DataTemplate.

So, simple set DataContext of Button explicitly using XAML using RelativeSource.

Example,

<Button Content="{Binding Data}" DataContext="{Binding vm1, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" /> 

Playing with DataContext in code is not a good idea.

AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38
1

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

Community
  • 1
  • 1
blindmeis
  • 22,175
  • 7
  • 55
  • 74