将只读GUI属性推回ViewModel [英] Pushing read-only GUI properties back into ViewModel
问题描述
具体来说,我的GUI包含一个FlowDocumentPageViewer,从FlowDocument一次显示一页。 FlowDocumentPageViewer公开了两个只读依赖属性,名为CanGoToPreviousPage和CanGoToNextPage。我想要我的ViewModel总是知道这两个View属性的值。
我想我可以用OneWayToSource数据绑定来执行此操作:
< FlowDocumentPageViewer
CanGoToNextPage ={Binding NextPageAvailable,Mode = OneWayToSource}...>
如果允许的话,这将是完美的:每当FlowDocumentPageViewer的CanGoToNextPage属性改变时,新值将被推下ViewModel的NextPageAvailable属性,这正是我想要的。
不幸的是,这不编译:我收到一个错误,说'CanGoToPreviousPage'属性是只读的,不能从标记设置。显然,只读属性不支持任何类型的数据绑定,即使是相对于该属性的只读数据绑定。
I可以使我的ViewModel的属性为DependencyProperties,并以另一种方式使OneWay绑定,但是对于分离关系的违规我并不感到失望(ViewModel需要引用View,哪个MVVM数据绑定应该避免) 。
FlowDocumentPageViewer不会公开CanGoToNextPageChanged事件,我不知道从DependencyProperty获取更改通知的任何好方法,而不是创建另一个DependencyProperty绑定
如何让ViewModel通知视图的只读属性的更改?
是的,过去我已经用
ActualWidth
和完成了这个任务。 ActualHeight
属性,这两个属性都是只读的。我创建了一个附加的行为,具有 ObservedWidth
和 ObservedHeight
附加属性。它还有一个 Observe
属性,用于执行初始连接。用法如下所示: < UserControl ...
SizeObserver.Observe = True
SizeObserver.ObservedWidth ={Binding Width,Mode = OneWayToSource}
SizeObserver.ObservedHeight ={Binding Height,Mode = OneWayToSource}
所以视图模型有宽度
和高度
属性始终与 ObservedWidth
和 ObservedHeight
附加属性同步。 Observe
属性简单地附加到 FrameworkElement
SizeChanged 事件>。在句柄中,它更新其 ObservedWidth
和 ObservedHeight
属性。 Ergo,视图模型的宽度
和 Height
始终与 ActualWidth
和
。 UserControl
的ActualHeight
可能不是完美的解决方案(我同意 - 只读DPs 应该支持 OneWayToSource
bindings),但它的工作原理,它支持MVVM模式。显然, ObservedWidth
和 ObservedHeight
DP是不只读。
更新:这里是实现上述功能的代码:
public static class SizeObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
Observe,
typeof(bool),
typeof(SizeObserver ),
新的FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached(
ObservedWidth,
typeof(double),
typeof(SizeObserver));
public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached(
ObservedHeight,
typeof(double),
typeof(SizeObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull(frameworkElement);
return(bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement,bool observe)
{
frameworkElement.AssertNotNull(frameworkElement);
frameworkElement.SetValue(ObserveProperty,观察);
}
public static double GetObservedWidth(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull(frameworkElement);
return(double)frameworkElement.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(FrameworkElement frameworkElement,double observeWidth)
{
frameworkElement.AssertNotNull(frameworkElement);
frameworkElement.SetValue(ObservedWidthProperty,observeWidth);
}
public static double GetObservedHeight(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull(frameworkElement);
return(double)frameworkElement.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(FrameworkElement frameworkElement,double observeHeight)
{
frameworkElement.AssertNotNull(frameworkElement);
frameworkElement.SetValue(ObservedHeightProperty,observeHeight);
}
private static void OnObserveChanged(DependencyObject dependencyObject,DependencyPropertyChangedEventArgs e)
{
var frameworkElement =(FrameworkElement)dependencyObject;
if((bool)e.NewValue)
{
frameworkElement.SizeChanged + = OnFrameworkElementSizeChanged;
UpdateObservedSizesForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.SizeChanged - = OnFrameworkElementSizeChanged;
}
}
private static void OnFrameworkElementSizeChanged(object sender,SizeChangedEventArgs e)
{
UpdateObservedSizesForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement)
{
// WPF 4.0向上
frameworkElement.SetCurrentValue(ObservedWidthProperty,frameworkElement.ActualWidth);
frameworkElement.SetCurrentValue(ObservedHeightProperty,frameworkElement.ActualHeight);
// WPF 3.5和之前
//// SetObservedWidth(frameworkElement,frameworkElement.ActualWidth);
//// SetObservedHeight(frameworkElement,frameworkElement.ActualHeight);
}
}
I want to write a ViewModel that always knows the current state of some read-only dependency properties from the View.
Specifically, my GUI contains a FlowDocumentPageViewer, which displays one page at a time from a FlowDocument. FlowDocumentPageViewer exposes two read-only dependency properties called CanGoToPreviousPage and CanGoToNextPage. I want my ViewModel to always know the values of these two View properties.
I figured I could do this with a OneWayToSource databinding:
<FlowDocumentPageViewer
CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>
If this was allowed, it would be perfect: whenever the FlowDocumentPageViewer's CanGoToNextPage property changed, the new value would get pushed down into the ViewModel's NextPageAvailable property, which is exactly what I want.
Unfortunately, this doesn't compile: I get an error saying 'CanGoToPreviousPage' property is read-only and cannot be set from markup. Apparently read-only properties don't support any kind of databinding, not even databinding that's read-only with respect to that property.
I could make my ViewModel's properties be DependencyProperties, and make a OneWay binding going the other way, but I'm not crazy about the separation-of-concerns violation (ViewModel would need a reference to the View, which MVVM databinding is supposed to avoid).
FlowDocumentPageViewer doesn't expose a CanGoToNextPageChanged event, and I don't know of any good way to get change notifications from a DependencyProperty, short of creating another DependencyProperty to bind it to, which seems like overkill here.
How can I keep my ViewModel informed of changes to the view's read-only properties?
Yes, I've done this in the past with the ActualWidth
and ActualHeight
properties, both of which are read-only. I created an attached behavior that has ObservedWidth
and ObservedHeight
attached properties. It also has an Observe
property that is used to do the initial hook-up. Usage looks like this:
<UserControl ...
SizeObserver.Observe="True"
SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"
So the view model has Width
and Height
properties that are always in sync with the ObservedWidth
and ObservedHeight
attached properties. The Observe
property simply attaches to the SizeChanged
event of the FrameworkElement
. In the handle, it updates its ObservedWidth
and ObservedHeight
properties. Ergo, the Width
and Height
of the view model is always in sync with the ActualWidth
and ActualHeight
of the UserControl
.
Perhaps not the perfect solution (I agree - read-only DPs should support OneWayToSource
bindings), but it works and it upholds the MVVM pattern. Obviously, the ObservedWidth
and ObservedHeight
DPs are not read-only.
UPDATE: here's code that implements the functionality described above:
public static class SizeObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(SizeObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached(
"ObservedWidth",
typeof(double),
typeof(SizeObserver));
public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached(
"ObservedHeight",
typeof(double),
typeof(SizeObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObserveProperty, observe);
}
public static double GetObservedWidth(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(FrameworkElement frameworkElement, double observedWidth)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedWidthProperty, observedWidth);
}
public static double GetObservedHeight(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(FrameworkElement frameworkElement, double observedHeight)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedHeightProperty, observedHeight);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.SizeChanged += OnFrameworkElementSizeChanged;
UpdateObservedSizesForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged;
}
}
private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateObservedSizesForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement)
{
// WPF 4.0 onwards
frameworkElement.SetCurrentValue(ObservedWidthProperty, frameworkElement.ActualWidth);
frameworkElement.SetCurrentValue(ObservedHeightProperty, frameworkElement.ActualHeight);
// WPF 3.5 and prior
////SetObservedWidth(frameworkElement, frameworkElement.ActualWidth);
////SetObservedHeight(frameworkElement, frameworkElement.ActualHeight);
}
}
这篇关于将只读GUI属性推回ViewModel的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!