将只读的依赖属性推回ViewModel

前言

场景:在wpf的页面中使用了ListView,当控件的实际高度发生改变时(如改变窗体大小),新的值会推送到ViewModel,重新计算ListView当前页显示的行数。

但ActualHeight是wpf中的只读属性,xaml允许只读属性作为数据绑定的源,而不是作为绑定的目标。

如果强行设置ActualHeight绑定ViewModel中的属性

<ListView 
    ActualHeight="{Binding ListViewActualHeight, Mode=OneWayToSource}" .../>

编译时将会收到错误信息:“ActualHeight”属性是只读的,且无法从标记设置”。

解决方案

创建附加行为,它具有ObservedWidth和ObservedHeight附加属性。还有一个Observed属性,用于进行初始连接。

下面为创建的SizeObserver类:

    //Observe属性

    public static bool GetObserve(FrameworkElement elem)
    {
        return (bool)elem.GetValue(ObserveProperty);
    }

    public static void SetObserve(
        FrameworkElement elem, bool value)
    {
        elem.SetValue(ObserveProperty, value);
    }

    public static readonly DependencyProperty ObserveProperty =
        DependencyProperty.RegisterAttached("Observe", typeof(bool), typeof(SizeObserver),
        new UIPropertyMetadata(false, OnObserveChanged));

    static void OnObserveChanged(
        DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement elem = depObj as FrameworkElement;
        if (elem == null)
            return;

        if (e.NewValue is bool == false)
            return;

        if ((bool)e.NewValue)
            elem.SizeChanged += OnSizeChanged;
        else
            elem.SizeChanged -= OnSizeChanged;
    }

    static void OnSizeChanged(object sender, RoutedEventArgs e)
    {
        if (!Object.ReferenceEquals(sender, e.OriginalSource))
            return;

        FrameworkElement elem = e.OriginalSource as FrameworkElement;
        if (elem != null)
        {
            //SetObservedWidth(elem, elem.ActualWidth);
            SetObservedHeight(elem, elem.ActualHeight);
        }
    }
    //ObservedWidth属性

    public static double GetObservedWidth(DependencyObject obj)
    {
        return (double)obj.GetValue(ObservedWidthProperty);
    }

    public static void SetObservedWidth(DependencyObject obj, double value)
    {
        obj.SetValue(ObservedWidthProperty, value);
    }

    // Using a DependencyProperty as the backing store for ObservedWidth.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ObservedWidthProperty =
        DependencyProperty.RegisterAttached("ObservedWidth", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));
    //ObservedHeight属性

    public static double GetObservedHeight(DependencyObject obj)
    {
        return (double)obj.GetValue(ObservedHeightProperty);
    }

    public static void SetObservedHeight(DependencyObject obj, double value)
    {
        obj.SetValue(ObservedHeightProperty, value);
    }

    // Using a DependencyProperty as the backing store for ObservedHeight.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ObservedHeightProperty =
        DependencyProperty.RegisterAttached("ObservedHeight", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));

Observe属性附加到FrameworkElement的SizeChanged事件。在句柄中它更新ObservedWidth和ObservedHeight的值。因此ViewModel中的属性值能够和ListView的实际值同步。

在xaml页面进行设置:

// 在页面头部引入SizeObserver类所在路径
<Window ...
        xmlns:u="clr-namespace:MyWpf.Utility"
// 设置ListView 绑定ViewModel中的属性(ListViewActualHeight为ViewModel中定义的属性)
<ListView ...
        u:SizeObserver.Observe="True" 
        u:SizeObserver.ObservedHeight="{Binding ListViewActualHeight, Mode=OneWayToSource}">
</ListView>    

 

 

参考

https://*.com/questions/1083224/pushing-read-only-gui-properties-back-into-viewmodel#

https://blog.machinezoo.com/datapipe-pushing-read-only-dependency

 

上一篇:类与模块化开发


下一篇:Excel表格中汉字转拼音