在MVVM应用程序中切换ViewModel时出现BindingExpression路径错误

时间:2013-08-29 17:18:33

标签: c# wpf mvvm .net-3.5

首先,首先是一些背景。如果您熟悉该问题,请跳至BindingExpression部分。这是我在WPF中的第一个主要项目,所以我对MVVM模式还很陌生。 Here是我发现的唯一一个类似的问题,其黯淡的回答并不能让我感到非常兴奋。

我正在/正在构建一个.NET 3.5 WPF应用程序,而我正在使用MVVM(自己实现,没有框架)。在此范围内,我有一些ViewsViewModels。它们分别位于主ApplicationViewApplicationViewModel内。

我更改视图的方式是使用ApplicationView中的XAML DataTemplate元素,如下所示:

<DataTemplate DataType="{x:Type viewmodels:InitViewModel}">
    <views:InitView />
</DataTemplate>

然后在主体中我有一个ContentControl,它绑定到ApplicationViewModel

中的一个属性
<ContentControl Content="{Binding CurrentPageViewModel}"/>

当我运行应用程序时,所有这些似乎都能正常工作,并完全符合预期。但是,当我在运行后查看Debug输出时,我会收到很多BindingExpression错误。

这是一个例子。我的SplashText中有一个属性InitViewModel。这绑定到初始屏幕(InitView)中的文本块。当启动画面结束并切换视图模型时,我得到以下内容:

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')

我理解这是因为绑定仍然存在,但DataContext的CurrentPageViewModel属性已更改。所以我想知道的是:

  • 这是一个稍纵即逝的问题,即在没有使用时处理的意见是什么,或者它们(和坏的绑定)无限期地存在于记忆中?
  • 在视图处于非活动状态时,有没有办法清理或停用这些绑定?
  • 如果我单独留下这些,会对我的申请产生什么样的表现?
  • 是否有更好的方法来切换视图以避免此问题?

提前致谢,并为整体问题道歉。

编辑03/09/13 - 感谢Jehof,Francesco De Lisi和Faster Solutions指出将子视图datacontext设置为{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}是没有意义的,因为ContentControl会处理datacontext。

4 个答案:

答案 0 :(得分:2)

您的具体示例在.NET 4.5中无法重现,这可能意味着Microsoft已经解决了此问题。

然而,当Content和ContentTemplate都是数据绑定时,存在类似的问题。我将解决这个问题,如果有人还在使用它,它也可能解决.NET 3.5中的问题。例如:

<ContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}" />

当ContentTemplate由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>

在这两种情况下,都会遇到类似于观察到的OP的绑定错误。

这里的技巧是确保以正确的顺序执行对Content和ContentTemplate的更改,以防止绑定错误。我写了DelayedContentControl,确保Content和ContentTemplate能够以正确的顺序同时更改。

<jc:DelayedContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}">

同样适用于DataTrigger案例。

您可以从我的开源JungleControls library获取DelayedContentControl。

答案 1 :(得分:1)

当您的财产属于另一个生成错误的DataContext属性时,MainMenuViewModel似乎转到ViewModel

启动画面前后的CurrentPageViewModel值会在切换视图时失去Binding

问题归结为DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"

事实上,CurrentPageViewModel = InitViewModel当你的应用程序启动时,问题是每个View都有相同的DataContext(即InitViewModel,但我确定ViewModels没有满足视图绑定的整个所需属性池。 一个例子要理解:

ViewXPropertyX绑定,在ViewModelX中进行管理。 ViewY具有PropertyY的约束力,在ViewModelY中进行管理。 两者都有DataContext = CurrentViewModel

在启动CurrentViewModel = ViewModelX上,ViewX和ViewY都有 DataContext = ViewModelX 。但这是错误!并且可能会产生错误。

我通常做的是在View类中使用相应的View Model设置DataContext(如果您愿意,使用cs或XAML)以确保它适合。然后,在需要时,我每次切换页面时都会调用刷新方法来更新我的值。如果您有共享属性,请考虑使用模型来集中您的信息(和值)。

来自http://wildermuth.com/images/mvvm_layout.png

的示例图片

enter image description here

显然,视图是由MainWindow包装的控件。

希望很清楚。

答案 2 :(得分:0)

让我们按顺序回答您的问题:

  1. 您可能已经知道了答案。当.Net垃圾收集它时,它将从堆中删除您的View对象。但是在此之前,您的View对象仍然绑定到页面上的主DataContext,并将对DataContext更改的事件作出反应。
  2. 显而易见的事情是将Views DataContext设置为null。 DataContext是一个依赖项属性,因此null值范围只是您的View。
  3. 正如另一个/黯淡的回答所说的那样,它会减慢你的速度,但不是很多。我不会太担心这个。
  4. 是。这是关于视图导航选项的有用线程:View Navigation Options
  5. 我还建议看一个框架。像MVVM Light这样轻量级的东西可以通过很少的集成为您解决一系列问题。它的ViewModelLocator模式也可以做你正在做的事情,但没有副作用,并提供了一大堆清理选项。

答案 3 :(得分:0)

您可以在Views

中省略DataContext的绑定
DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"

因为DataContext的{​​{1}}是View的{​​{1}},并且由DataContext - 属性的绑定设置。

因此,当您的媒体资源ContentControl设置为Content时,CurrentPageViewModel会将InitViewModel用作ContentControl并使用InitViewModel作为DataContext,它将自己的DataContext设置为InitView的DataContext。