博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[UWP]附加属性2:实现一个Canvas
阅读量:5924 次
发布时间:2019-06-19

本文共 8584 字,大约阅读时间需要 28 分钟。

原文:

5. 附加属性实践:自定义Canvas

附加属性在UWP中是一个十分重要的组成部分,很多功能都依赖于附加属性实现,典型的例子是常用的Grid和Canvas。通常附加属性有三个使用场景:插入属性、触发行为、当做缓存。可以参考以下提供的MyCanvas示例理解这三点。

5.1 插入属性

这里实现的MyCanvas继承自Panel,是一个十分简单的类(作为示例并没有十分严格的验证等代码,所以只有几十行代码),它实现了和Canvas类似的布局并且提供了Left和Right两个附加属性。使用方式如下:

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{
/// // 从指定元素获取 Left 依赖项属性的值。 /// /// The element from which the property value is read. ///
Left 依赖项属性的值
public static double GetLeft(DependencyObject obj) {
return (double)obj.GetValue(LeftProperty); } /// /// 将 Left 依赖项属性的值设置为指定元素。 /// /// The element on which to set the property value. /// The property value to set. public static void SetLeft(DependencyObject obj, double value) {
obj.SetValue(LeftProperty, value); } /// /// 标识 Left 依赖项属性。 /// public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(double), typeof(MyCanvas), new PropertyMetadata(0d, OnPositionChanged)); /// // 从指定元素获取 Top 依赖项属性的值。 /// /// The element from which the property value is read. ///
Top 依赖项属性的值
public static double GetTop(DependencyObject obj) {
return (double)obj.GetValue(TopProperty); } /// /// 将 Top 依赖项属性的值设置为指定元素。 /// /// The element on which to set the property value. /// The property value to set. public static void SetTop(DependencyObject obj, double value) {
obj.SetValue(TopProperty, value); } /// /// 标识 Top 依赖项属性。 /// public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached("Top", typeof(double), typeof(MyCanvas), new PropertyMetadata(0d, OnPositionChanged)); /// // 从指定元素获取 CanvasParent 依赖项属性的值。 /// /// The element from which the property value is read. ///
CanvasParent 依赖项属性的值
public static MyCanvas GetCanvasParent(DependencyObject obj) {
return (MyCanvas)obj.GetValue(CanvasParentProperty); } /// /// 将 CanvasParent 依赖项属性的值设置为指定元素。 /// /// The element on which to set the property value. /// The property value to set. public static void SetCanvasParent(DependencyObject obj, MyCanvas value) {
obj.SetValue(CanvasParentProperty, value); } /// /// 标识 CanvasParent 依赖项属性。 /// 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,有兴趣可以看看它的源码:

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{
/// // 从指定元素获取 AttachedObject 依赖项属性的值。 /// /// The element from which the property value is read. ///
AttachedObject 依赖项属性的值
public static Test GetAttachedObject(DependencyObject obj) {
return (Test)obj.GetValue(AttachedObjectProperty); } /// /// 将 AttachedObject 依赖项属性的值设置为指定元素。 /// /// The element on which to set the property value. /// The property value to set. public static void SetAttachedObject(DependencyObject obj, Test value) {
obj.SetValue(AttachedObjectProperty, value); } /// /// 标识 AttachedObject 依赖项属性。 /// 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. 参考

转载地址:http://syavx.baihongyu.com/

你可能感兴趣的文章
尼日利亚国内最大航空公司Arik Air遭遇数据泄露
查看>>
if (log.isDebugEnabled()) 使用场景
查看>>
2019年首批!网易易盾加固系统通过中国反网络病毒联盟认证
查看>>
Hadoop集群环境搭建
查看>>
Quartz2D
查看>>
iOS通讯录
查看>>
JS单例模式
查看>>
数据库查询性能优化之利器—索引(二)
查看>>
django自定义管理表单
查看>>
栈与queue
查看>>
设置环境变量
查看>>
嵌入式主板的应用领域
查看>>
你的信息只值1毛钱 大数据时代如何不做“透明人”?
查看>>
非win7系统打开H3C的注意事项
查看>>
基础篇|PHP如何解决网站大流量和高并发
查看>>
安装RabbitMQ(一)
查看>>
Java学习方法:Java学习路线分享
查看>>
文件查找和压缩
查看>>
来,赏一赏咱敬业的春
查看>>
对于java我的看法
查看>>