Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BindingExpression path errors when switching ViewModels in MVVM application

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.

like image 460
Will Faithfull Avatar asked Aug 29 '13 17:08

Will Faithfull


2 Answers

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.

like image 145
Robert Važan Avatar answered Sep 27 '22 18:09

Robert Važan


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

enter image description here

Obviously the Views are the Controls wrapped by your MainWindow.

Hope it's clear.

like image 20
Francesco De Lisi Avatar answered Sep 27 '22 17:09

Francesco De Lisi