将只读GUI属性推回ViewModel [英] Pushing read-only GUI properties back into ViewModel

查看:199
本文介绍了将只读GUI属性推回ViewModel的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写一个ViewModel,它始终知道View中的一些只读依赖属性的当前状态。



具体来说,我的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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆