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:
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With