我想编写一个ViewModel,它始终知道View中某些只读依赖项属性的当前状态 .
具体来说,我的GUI包含一个FlowDocumentPageViewer,它一次从FlowDocument显示一个页面 . FlowDocumentPageViewer公开了两个名为CanGoToPreviousPage和CanGoToNextPage的只读依赖项属性 . 我希望我的ViewModel始终知道这两个View属性的值 .
我想我可以使用OneWayToSource数据绑定来做到这一点:
<FlowDocumentPageViewer
CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>
如果这是允许的,那将是完美的:每当FlowDocumentPageViewer的CanGoToNextPage属性发生更改时,新值将被推送到ViewModel的NextPageAvailable属性,这正是我想要的 .
不幸的是,这不编译:我收到一个错误说 'CanGoToPreviousPage' property is read-only and cannot be set from markup. 显然只读属性不支持任何类型的数据绑定,甚至不支持与该属性相关的只读数据绑定 .
我可以让我的ViewModel的属性为DependencyProperties,并使OneWay绑定以另一种方式运行,但我并不为关注点分离违规而疯狂(ViewModel需要对View的引用,MVVM数据绑定应该避免) .
FlowDocumentPageViewer不公开CanGoToNextPageChanged事件,我不知道从DependencyProperty获取更改通知的任何好方法,没有创建另一个DependencyProperty来绑定它,这在这里看起来有点过分 .
如何让ViewModel了解视图的只读属性的更改?
6 回答
是的,我过去使用
ActualWidth
和ActualHeight
属性完成了这两个属性,这两个属性都是只读的 . 我创建了一个具有ObservedWidth
和ObservedHeight
附加属性的附加行为 . 它还有一个Observe
属性,用于执行初始连接 . 用法如下:因此,视图模型具有
Width
和Height
属性,这些属性始终与ObservedWidth
和ObservedHeight
附加属性同步 .Observe
属性只是附加到FrameworkElement
的SizeChanged
事件 . 在句柄中,它更新了ObservedWidth
和ObservedHeight
属性 . 因此,视图模型的Width
和Height
始终与UserControl
的ActualWidth
和ActualHeight
同步 .也许不是完美的解决方案(我同意 - 只读DP应该支持
OneWayToSource
绑定),但它可以工作并且它支持MVVM模式 . 显然,ObservedWidth
和ObservedHeight
DP不是只读的 .更新:这是实现上述功能的代码:
我使用的通用解决方案不仅适用于ActualWidth和ActualHeight,还适用于至少在读取模式下可以绑定的任何数据 .
如果ViewportWidth和ViewportHeight是视图模型的属性,则标记看起来像这样
这是自定义元素的源代码
如果有其他人感兴趣,我在这里编写了肯特解决方案的近似值:
随意在您的应用程序中使用它 . 它运作良好 . (谢谢肯特!)
这是我在这里博客的另一个解决方案"bug":
OneWayToSource Binding for ReadOnly Dependency Property
它通过使用两个依赖项属性,侦听器和镜像来工作 . Listener将OneWay绑定到TargetProperty,并在PropertyChangedCallback中更新Mirror属性,该属性将OneWayToSource绑定到Binding中指定的任何内容 . 我称之为
PushBinding
,它可以在任何只读的依赖属性上设置Download Demo Project Here .
它包含源代码和简短的示例用法,如果您对实现细节感兴趣,请访问my WPF blog .
最后一点,自.NET 4.0以来,我们甚至更远离内置支持,因为OneWayToSource Binding reads the value back from the Source after it has updated it
我喜欢Dmitry Tashkinov的解决方案!然而它在设计模式下撞毁了我的VS.这就是为什么我在OnSourceChanged方法中添加了一行:
我认为它可以做得更简单一些:
XAML:
CS: