前言
场景:在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