[UWP]附加属性2:实现一个Canvas
5. 附加属性实践:自定义Canvas
附加属性在UWP中是一个十分重要的组成部分,很多功能都依赖于附加属性实现,典型的例子是常用的Grid和Canvas。通常附加属性有三个使用场景:插入属性、触发行为、当做缓存。可以参考以下提供的MyCanvas示例理解这三点。
5.1 插入属性
这里实现的MyCanvas继承自Panel,是一个十分简单的类(作为示例并没有十分严格的验证等代码,所以只有几十行代码),它实现了和Canvas类似的布局并且提供了Left和Right两个附加属性。使用方式如下:
<local:MyCanvas> <Rectangle local:MyCanvas.Left="50" local:MyCanvas.Top="50" Height="100" Width="100" Fill="Green" /> </local:MyCanvas>
Panel最核心的代码是ArrangeOverride,简单来说,它负责定位Children中的所有元素。MyCanvas读取子元素的定位信息MyCanvas.Left和MyCanvas.Top后对其进行定位,子元素自身并没有这两个属性,只有通过附加属性插入。
public static double GetLeft(DependencyObject obj) { return (double)obj.GetValue(LeftProperty); } public static void SetLeft(DependencyObject obj, double value) { obj.SetValue(LeftProperty, value); } public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(double), typeof(MyCanvas), new PropertyMetadata(0d)); public static double GetTop(DependencyObject obj) { return (double)obj.GetValue(TopProperty); } public static void SetTop(DependencyObject obj, double value) { obj.SetValue(TopProperty, value); } public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached("Top", typeof(double), typeof(MyCanvas), new PropertyMetadata(0d)); protected override Size ArrangeOverride(Size arrangeSize) { foreach (UIElement child in Children) { double left = GetLeft(child); double top = GetTop(child); child.Arrange(new Rect(new Point(left, top), child.DesiredSize)); } return arrangeSize; }
5.2 触发行为
ArrangeOverride是MyCanvas被加载到VisualTree上后被调用的,想要监视MyCanvas.Left或MyCanvas.Top属性并在每次更改后触发ArrangeOverride更改布局,可以在这两个属性的PropertyMetadata中添加PropertyChangedCallback,代码如下:
public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached("Top", typeof(double), typeof(MyCanvas), new PropertyMetadata(0d, OnLeftChanged)); private static void OnLeftChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { double oldValue = (double)args.OldValue; double newValue = (double)args.NewValue; if (oldValue == newValue) return; var parent = VisualTreeHelper.GetParent(obj) as MyCanvas; if (parent != null) parent.InvalidateArrange(); }
当Left改变时调用OnLeftChanged,这里DependencyObject obj就是被附加了Left属性的子元素。通过 VisualTreeHelper.GetParent找到它的父元素,调用父元素的InvalidateArrange再次触发ArrangeOverride函数。
5.3 当做缓存
有时我会很偷懒地把附加属性当做缓存来用。譬如在上面的代码中,假设VisualTreeHelper.GetParent是一个很耗时的操作(只是假设),我会把parent放到缓存里面,而这个缓存还是用附加属性实现的。
private static void OnLeftChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { double oldValue = (double)args.OldValue; double newValue = (double)args.NewValue; if (oldValue == newValue) return; var parent = GetCanvasParent(obj); if (parent == null) { parent = VisualTreeHelper.GetParent(obj) as MyCanvas; SetCanvasParent(obj, parent); } if (parent != null) parent.InvalidateArrange(); }
注意: 实际上VisualTreeHelper.GetParent函数并没有十分耗时,所以这里是没必要这样写的。
5.4 完整的MyCanvas代码
public class MyCanvas : Panel { /// <summary> // 从指定元素获取 Left 依赖项属性的值。 /// </summary> /// <param name="obj">The element from which the property value is read.</param> /// <returns>Left 依赖项属性的值</returns> public static double GetLeft(DependencyObject obj) { return (double)obj.GetValue(LeftProperty); } /// <summary> /// 将 Left 依赖项属性的值设置为指定元素。 /// </summary> /// <param name="obj">The element on which to set the property value.</param> /// <param name="value">The property value to set.</param> public static void SetLeft(DependencyObject obj, double value) { obj.SetValue(LeftProperty, value); } /// <summary> /// 标识 Left 依赖项属性。 /// </summary> public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(double), typeof(MyCanvas), new PropertyMetadata(0d, OnPositionChanged)); /// <summary> // 从指定元素获取 Top 依赖项属性的值。 /// </summary> /// <param name="obj">The element from which the property value is read.</param> /// <returns>Top 依赖项属性的值</returns> public static double GetTop(DependencyObject obj) { return (double)obj.GetValue(TopProperty); } /// <summary> /// 将 Top 依赖项属性的值设置为指定元素。 /// </summary> /// <param name="obj">The element on which to set the property value.</param> /// <param name="value">The property value to set.</param> public static void SetTop(DependencyObject obj, double value) { obj.SetValue(TopProperty, value); } /// <summary> /// 标识 Top 依赖项属性。 /// </summary> public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached("Top", typeof(double), typeof(MyCanvas), new PropertyMetadata(0d, OnPositionChanged)); /// <summary> // 从指定元素获取 CanvasParent 依赖项属性的值。 /// </summary> /// <param name="obj">The element from which the property value is read.</param> /// <returns>CanvasParent 依赖项属性的值</returns> public static MyCanvas GetCanvasParent(DependencyObject obj) { return (MyCanvas)obj.GetValue(CanvasParentProperty); } /// <summary> /// 将 CanvasParent 依赖项属性的值设置为指定元素。 /// </summary> /// <param name="obj">The element on which to set the property value.</param> /// <param name="value">The property value to set.</param> public static void SetCanvasParent(DependencyObject obj, MyCanvas value) { obj.SetValue(CanvasParentProperty, value); } /// <summary> /// 标识 CanvasParent 依赖项属性。 /// </summary> public static readonly DependencyProperty CanvasParentProperty = DependencyProperty.RegisterAttached("CanvasParent", typeof(MyCanvas), typeof(MyCanvas), new PropertyMetadata(null)); private static void OnPositionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { double oldValue = (double)args.OldValue; double newValue = (double)args.NewValue; if (oldValue == newValue) return; var parent = VisualTreeHelper.GetParent(obj) as MyCanvas; if (parent != null) parent.InvalidateArrange(); } protected override Size ArrangeOverride(Size arrangeSize) { foreach (UIElement child in Children) { double left = GetLeft(child); double top = GetTop(child); child.Arrange(new Rect(new Point(left, top), child.DesiredSize)); } return arrangeSize; } protected override Size MeasureOverride(Size constraint) { Size childConstraint = new Size(Double.PositiveInfinity, Double.PositiveInfinity); foreach (UIElement child in Children) { if (child == null) { continue; } child.Measure(childConstraint); } return new Size(); } }
这里的代码参考了WPF中的Canvas,有兴趣可以看看它的源码:Canvas
6. 内存回收
前面提过,依赖属性的值是以所依赖的对象及属性标识作为Key存放到HashTable中,附加属性作为依赖属性的一种特殊形式它的实现也是这样。既然这个HashTable一直存在,会不会作为Key的依赖对象也被迫存活,没有被回收?假设真是这样的话,设置了Grid.Row、Canvas.Left等属性的所有对象都被迫存活在内存中?
实际上并不需要担心这个问题,微软提供了名为ConditionalWeakTable的类并使用这个类实现依赖属性机制,保证了依赖属性的内存回收。
参考这段代码:
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); Loaded += MainPage_Loaded; var button = new MyButton(); Test test = new Test(); button.SetValue(Test.AttachedObjectProperty, test); this.LayoutRoot.Children.Add(button); } private void MainPage_Loaded(object sender, RoutedEventArgs e) { LayoutRoot.Children.Clear(); Task.Factory.StartNew(async () => { while (true) { await Task.Delay(TimeSpan.FromSeconds(1)); GC.Collect(); } }); } } public class MyButton : Button { ~MyButton() { Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss fff:") + "MyButton Finalize"); } } public class Test : DependencyObject { /// <summary> // 从指定元素获取 AttachedObject 依赖项属性的值。 /// </summary> /// <param name="obj">The element from which the property value is read.</param> /// <returns>AttachedObject 依赖项属性的值</returns> public static Test GetAttachedObject(DependencyObject obj) { return (Test)obj.GetValue(AttachedObjectProperty); } /// <summary> /// 将 AttachedObject 依赖项属性的值设置为指定元素。 /// </summary> /// <param name="obj">The element on which to set the property value.</param> /// <param name="value">The property value to set.</param> public static void SetAttachedObject(DependencyObject obj, Test value) { obj.SetValue(AttachedObjectProperty, value); } /// <summary> /// 标识 AttachedObject 依赖项属性。 /// </summary> public static readonly DependencyProperty AttachedObjectProperty = DependencyProperty.RegisterAttached("AttachedObject", typeof(Test), typeof(Test), new PropertyMetadata(null)); ~Test() { Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss fff:") + "Test Finalize"); } }
运行后输出:
02:06:14 741:MyButton Finalize
02:06:14 747:Test Finalize
可以看出在MyButton及附加的Test对象都被确实被回收了。
7. 参考
附加属性概述
自定义附加属性
Silverlight附加属性概述
Silverlight自定义的附加属性
原文地址:https://www.cnblogs.com/dino623/p/6359729.html
相关推荐
-
Unity项目中UI同学需知的程序相关要点 C#
2019-9-1
-
.NET中的各种池 C#
2019-8-30
-
基于任务的异步编程模式(TAP),基于任务的异步编程模式(TAP)的错误处理 C#
2019-9-18
-
C#读取Excel导出的CSV文件 C#
2019-9-1
-
C#相等性 – “==” C#
2019-6-15
-
Centos7 防火墙 firewalld 实用操作 C#
2019-4-18
-
asp.netcore 深入了解配置文件加载过程 C#
2019-4-2
-
asp.net core 自定义异常处理中间件 C#
2019-8-29
-
Azure AD(二)调用受Microsoft 标识平台保护的 ASP.NET Core Web API 上 C#
2020-6-12
-
探讨.NET Core中实现AES加密和解密以及.NET Core为我们提供了什么方便! C#
2019-5-12