I've got a dilemma regarding the DataContext. Let's inspect the following piece of XAML:
<Window xmlns:my="clr-namespace:MyNamespace.Controls"
... >
...
<my:MyControl Name="{Binding Prop1}" Value="{Binding Prop2}" />
</Window>
Obviously, the Window's code-behind contains something like:
DataContext = someViewModel;
Author's intentions are clear - he wants to bind MyControl
's Name
and Value
to Window
's DataContext
's Prop1
and Prop2
. And this will of course work. Unless. (dramatic pause)
Unless MyControl
is a composite UserControl
, which also wants to take advantage of short notation of bindings and sets its DataContext
to its own viewmodel. Because then it will become clear, that the bindings in Window's
XAML actually bind to MyControl
's DataContext
(previously inherited from Window
's one) and now they will stop working (or worse, will keep working if MyControl
's viewmodel actually contains properties named Prop1
and Prop2
1).
In this particular case solution is to bind in Window
's code explicitly:
<Window x:Name="rootControl"
xmlns:my="clr-namespace:MyNamespace.Controls"
... >
...
<my:MyControl Name="{Binding ElementName=rootControl, Path=DataContext.Prop1}"
Value="{Binding ElementName=rootControl, Path=DataContext.Prop2}" />
</Window>
TL;DR If we're using short notation of bindings (when binding to DataContext
) we may encounter quite tough to nail bugs resulting from bindings suddenly pointing to wrong DataContext
.
My question is: how to use short binding notation without risk, that I'll bind to wrong DataContext
? Of course I may use the short notation when I'm sure, that I'll be using inherited DataContext
and long notation when I'm sure, that control will modify its DataContext
. But that "I'm sure" will work only until first mistake, which will consume another hour of debugging.
Maybe I'm not following some MVVM rule? E.g. for example DataContext
should be set only once on the top level and all composited controls should bind to something else?
1 I've gone through that, actually. The
Window
's DataContext
contained a property named (say) Prop
and the control replaced its DataContext
with a class, which also contained a property Prop
and everything worked fine. Problem appeared when I tried to use (unconsciously) the same pattern with non-matching property names.
By request:
Fragment of MyControl's code:
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
// Using a DependencyProperty as the backing store for Name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(MyControl), new PropertyMetadata(null));
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(MyControl), new PropertyMetadata(0));
Window's viewmodel:
public class WindowViewmodel : INotifyPropertyChanged
{
// (...)
public string Prop1
{
get
{
return prop1;
}
set
{
prop1 = value;
OnPropertyChanged("Prop1");
}
}
public int Prop2
{
get
{
return prop2;
}
set
{
prop2 = value;
OnPropertyChanged("Prop2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Now assume, that on changing of Name
and Value
dependency properties, MyControl
generates some viewmodel and executes the code:
model = new MyControlViewModel(Name, Value);
this.DataContext = model;
And internal MyControl
controls bind to this DataContext.
From now on, the original Name
and Value
bindings will no longer work.
within a usercontrol you should never set the datacontext to "this" or a new viewmodel. a developer/user of your MyUsercontrol expect that the datacontext inherit from top to bottom (from mainwindow to your myusercontrol).
your usercontrol xaml should use element binding
MyUserControl.xaml
this means your following code will work now in every situation
the property Prop1 from Datacontext mainwindow is bound to the DP Name from your MyUsercontrol and the Textblock.Text within your MyUsercontrol is bound to the DP Name.
I've never met such a problem. It seems to be a little bit theoretical to me but maybe because of my approach to working with DataContext in WPF.
DataContext
property. I set it manually only for windows.DataContext
property is set explicitly.DataContext
property for Windows is set to root ViewModel which contains child ViewModels, which contain...DataTemplate
ResourceDictionary
which contains mappings between all ViewModels and Views.And that's where I stopped reading. This is, imho, a MVVM anti-pattern.
The reason for this is twofold. First, you screw with anybody who is using the control. "Hey," you say, "you can't bind your stinky VM to my beautiful UI. You have to use MY custom VM!" But what if your VM is hard to use, lacks logic or features needed by the overall application? What happens when, to use your UI, we have to translate our VM/models back and forth with your VM? Pain in the butt.
Second is that your custom control is UI. Its logic is UI logic, and so it is unnecessary to use a view model. It is better to expose DependencyProperties on your control and update your UI as necessary. That way anybody can bind to your UI and use it with any model or view model.
You can solve your problems by simply not using what you call a 'composite control. While I understand that you want to encapsulate some functionality in the associated view model, you don't need to set the view model to the
UserControl.DataContext
internally.What I mean by this is that you can have a view model for any or all of your
UserControl
s, but they're data classes, not UI classes, so keep them out of the view code. If you use this method of addingDataTemplate
s intoResources
, then you won't need to set anyDataContext
properties at all:The final difference is that you should add your view model for your
UserControl
s as properties in a parent view model. This way, you still have no duplicated code (except maybe just a property declaration) and more importantly, you have noBinding
problems from mixingDataContext
values.UPDATE >>>
When using this
DataTemplate
method of hooking up views and view models, you can display your view byBinding
your view model property to theContent
property of aContentControl
like this:At run time, this
ContentControl
will be rendered as whatever view orUserControl
that you defined in theDataTemplate
of the relevant type for that property. Note that you shouldn't set thex:Key
of theDataTemplate
, otherwise you'd also need to set theContentControl.ContentTemplate
property and that can limit the possibilities afforded by this method.For example, without setting the
x:Key
property on yourDataTemplate
s, you could have a property of a base type and by setting it to different sub class, you can have different views for each from the oneContentControl
. That is the basis of all of my views... I have one property of a base class view model data bound like this example and to change views, I just change the property to a new view model that is derived from the base class.UPDATE 2 >>>
Here's the thing... you shouldn't have any 'proxy' object in your
UserControl
s doing anything... it should all be done through properties. So just declare aDependencyProperty
of the type of that object and supply it from the view model through dataBinding
. Doing it this way means that it will be easy to test the functionality of that class, whereas testing code behind views is not.And finally, yes, it's perfectly fine doing this in MVVM:
The overriding goal of MVVM is just to provide separation between the UI code and the view model code, so that we can easily test what's in the view models. That is why we try to remove as much functionality code from the views as possible.