在MVVM应用程序中切换ViewModel时出现BindingExpression路径错误
首先,一些背景。 如果您熟悉该问题,请跳至BindingExpression
部分。 这是我在WPF中的第一个主要项目,所以我对MVVM模式还很陌生。 这是我发现的唯一一个类似的问题,其黯淡的回答并不能让我感到非常兴奋。
我/我正在构建一个.NET 3.5 WPF应用程序,我正在使用MVVM(自己实现,没有框架)。 在这里,我有许多Views
和ViewModels
。 它们分别驻留在主ApplicationView
和ApplicationViewModel
。
我更改视图的方式是在ApplicationView
使用XAML DataTemplate元素,如下所示:
然后在主体中我有一个ContentControl,它绑定到ApplicationViewModel
的属性
当我运行应用程序时,所有这些似乎工作正常,并完全符合预期。 但是,当我在运行后查看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。
当您的属性属于另一个生成错误的ViewModel
时,您的DataContext
似乎会转到MainMenuViewModel
。
启动屏幕之前和之后的CurrentPageViewModel
值在切换视图时更改失去其Binding
。
问题归结于DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
实际上,当你的应用程序启动时, CurrentPageViewModel = InitViewModel
,但问题是每个View
都有相同的DataContext
(首先是InitViewModel
),但我确信ViewModels
没有满足视图绑定所需的整个所需属性池。 一个例子来理解:
ViewX
与PropertyX
绑定,在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的示例图片
显然,视图是由MainWindow包装的控件。
希望很清楚。
您的具体示例在.NET 4.5中不可重现,这可能意味着Microsoft已经解决了此问题。
然而,当Content和ContentTemplate都是数据绑定时,存在类似的问题。 我将解决这个问题,如果有人还在使用它,它也可能解决.NET 3.5中的问题。 例如:
或者当DataTemplate由DataTrigger确定时:
在这两种情况下,都会得到类似于观察到的OP的绑定错误。
这里的技巧是确保以正确的顺序执行对Content和ContentTemplate的更改,以防止绑定错误。 我编写了DelayedContentControl ,它确保Content和ContentTemplate能够以正确的顺序同时更改。
类似地,对于DataTrigger案例。
您可以从我的opensource JungleControls库中获取DelayedContentControl。
让我们按顺序回答你的问题:
- 你可能已经知道了答案。 当.Net垃圾收集它时,它将从堆中删除您的View对象。 但是在此之前,您的View对象仍然绑定到页面上的主DataContext,并将对DataContext更改的事件做出反应。
- 显而易见的事情是将Views DataContext设置为null。 DataContext是一个依赖项属性,因此null值范围将只是您的View。
- 正如另一个/黯淡的回答所说的那样,它会减慢你的速度,但不是很多。 我不会太担心这个。
- 是。 这是视图导航选项的有用线程: 查看导航选项
我还建议看一个框架。 像MVVM Light这样轻量级的东西可以通过很少的集成为您解决一系列问题。 它的ViewModelLocator模式也可以做你正在做的事情,但没有副作用,并提供了一大堆清理选项。
您可以在视图中省略DataContext的绑定
DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
因为View
的DataContext
是ContentControl
的DataContext
,它由Content
-Property的绑定设置。
因此,当您的属性CurrentPageViewModel
设置为InitViewModel
, ContentControl
将使用InitViewModel
作为DataContext
并使用InitView
作为ContentTemplate
,它将自己的DataContext设置为InitView的DataContext。