First things first, some context. If you're familiar with the problem, skip down to the BindingExpression
part. This is my first major project in WPF, so I am still quite new to the MVVM pattern. Here is the only other similar question I have found, whose lacklustre answer doesn't really enthuse me much.
I have/am building a .NET 3.5 WPF application and I am using MVVM (implemented myself, no framework). Within this, I have a number of Views
and ViewModels
. These reside within a master ApplicationView
and ApplicationViewModel
respectively.
The way I change views is through using XAML DataTemplate elements in the ApplicationView
, like so:
<DataTemplate DataType="{x:Type viewmodels:InitViewModel}">
<views:InitView />
</DataTemplate>
And then in the main body I have a ContentControl which binds to a property in ApplicationViewModel
<ContentControl Content="{Binding CurrentPageViewModel}"/>
When I run the application, all of this appears to work fine, and does exactly what is intended. However, when I look at the Debug output after the run, I get a lot of BindingExpression
errors.
Here is one for example. I have a property, SplashText
, in my InitViewModel
. This is bound to a textblock in the splash screen (InitView
). When the splash screen ends and I switch out the viewmodel, I get the following:
System.Windows.Data Error: 39 : BindingExpression path error: 'SplashText' property not found on 'object' ''MainMenuViewModel' (HashCode=680171)'. BindingExpression:Path=SplashText; DataItem='MainMenuViewModel' (HashCode=680171); target element is 'TextBox' (Name='FeedBackBox'); target property is 'Text' (type 'String')
I understand that this is because the bindings still exist, but the CurrentPageViewModel property of the DataContext has changed. So what I want to know is:
- Is this a fleeting problem, i.e. are the views disposed of when not being used or do they (and the bad bindings) sit there in memory indefinitely?
- Is there a way I can clean up or deactivate these bindings while the view is inactive?
- What sort of performance knock is it going to have on my application if I leave these alone?
- Is there a better way of switching views which avoids this problem?
Thanks in advance, and apologies for the monolithic question.
Edit 03/09/13 - Thanks to Jehof, Francesco De Lisi and Faster Solutions for pointing out that it is pointless to set sub-views datacontext as {Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}
because the ContentControl takes care of the datacontext.
It looks like your DataContext
goes to MainMenuViewModel
while your property belongs to another ViewModel
generating the error.
The CurrentPageViewModel
value before and after the splash screen changes losing its Binding
while switching view.
The problem is dued to DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
In fact, CurrentPageViewModel = InitViewModel
when your application starts, but the problem is that every View
has the same DataContext
(i.e. InitViewModel
at first) but I'm sure the ViewModels
haven't the entire pool of needed properties to satisfy view bindings.
An example to understand:
ViewX
has a binding to PropertyX
, managed in ViewModelX
.
ViewY
has a binding to PropertyY
, managed in ViewModelY
.
Both have DataContext = CurrentViewModel
.
On the startup CurrentViewModel = ViewModelX
and both ViewX and ViewY have DataContext = ViewModelX. But this is wrong! And probably will generate an error.
What I usually do is to set in the View class the DataContext (cs or XAML if you prefer) with the corresponding View Model to be sure it fits. Then, when needed, I call a refresh method to update my values every time I switch page. If you have shared properties consider to use a Model to centralize your informations (and values).
A sample image from http://wildermuth.com/images/mvvm_layout.png
Obviously the Views are the Controls wrapped by your MainWindow.
Hope it's clear.
Your specific example is not reproducible in .NET 4.5, which probably means Microsoft has fixed the problem meantime.
Nevertheless, a similar problem exists when Content and ContentTemplate are both data-bound. I am going to address that problem, which is also likely to solve problems in .NET 3.5 if anyone is still using it. For example:
<ContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}" />
Or when ContentTemplate is determined by DataTrigger:
<ContentControl Content="{Binding Content}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Choice}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
</DataTrigger>
<DataTrigger Binding="{Binding Choice}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
In both cases, one gets binding errors similar to those OP observed.
The trick here is to ensure that changes to Content and ContentTemplate are performed in just the right order to prevent binding errors. I've written DelayedContentControl, which ensures that Content and ContentTemplate are changed at the same time and in the right order.
<jc:DelayedContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}">
Similarly for the DataTrigger case.
You can get DelayedContentControl from my opensource JungleControls library.
Lets answer your questions in sequence:
- You probably already know the answer to this. When .Net garbage collects it'll remove your View object from the heap. But until this time your View object is still bound to the main DataContext on your page and will react to DataContext changed events.
- The obvious thing to do is to set the Views DataContext to null. DataContext is a dependency property so the null values scope will just be your View.
- As the other/lackluster answer said, it'll slow you down a bit but not a lot. I wouldn't worry too much about this.
- Yes. Here's a useful thread on view navigation options: View Navigation Options
I'd also suggest looking at a framework. Something light-weight like MVVM Light will solve a bunch of problems for you with very little integration. It's ViewModelLocator pattern also does what you're doing, but without the side-effects and provides a whole bunch of cleanup options.
You can omit the binding of the DataContext in your Views
DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
cause the DataContext
of your View
is the DataContext
of the ContentControl
and that gets set by your binding of the Content
-Property.
So, when your property CurrentPageViewModel
is set to an InitViewModel
the ContentControl
will use the InitViewModel
as DataContext
and use the InitView
as ContentTemplate
and it will set his own DataContext as DataContext of the InitView.