[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自定义的附加属性
[UWP]附加属性2:实现一个Canvas的更多相关文章
- 手把手教你使用 js 实现一个 Canvas 编辑器
手把手教你使用 js 实现一个 Canvas 编辑器 拖拽 缩放,等比缩放 导出 image 模版 撤销,重做 OOP,封装,继承,多态 发布库 CI/CD (gitlab/github) ... h ...
- [UWP]附加属性1:概述
1. 什么是附加属性(attached property ) 附加属性依赖属性的一种特殊形式,常见的Grid.Row,Canvas.Left都是附加属性. /// <summary> // ...
- [UWP]使用Picker实现一个简单的ColorPicker弹窗
在上一篇博文<[UWP]使用Popup构建UWP Picker>中我们简单讲述了一下使用Popup构建适用于MVVM框架下的弹窗层组件Picker的过程.但是没有应用实例的话可能体现不出P ...
- 用初中数学知识撸一个canvas环形进度条
周末好,今天给大家带来一款接地气的环形进度条组件vue-awesome-progress.近日被设计小姐姐要求实现这么一个环形进度条效果,大体由四部分组成,分别是底色圆环,进度弧,环内文字,进度圆点. ...
- 怎样创建一个canvas画布环境
1. 由于canvas画布在网页中, 所以需要在html中添加canvas标签: <!DOCTYPE html> <html lang="en"> < ...
- 为WPF, UWP 及 Xamarin实现一个简单的消息组件
原文地址:Implementing a simple messenger component for WPF, UWP and Xamarin 欢迎大家关注我的公众号:程序员在新西兰了解新西兰IT行业 ...
- 我的第一个canvas的作品:漫画对白编辑器
背景:一直都对canvas挺有有兴趣的,之前刚刚看了<HTML5 CANVAS基础教程>,写了篇读书笔记. 起因:老婆发来一张最近比较热的漫画图(友谊的小船说翻就翻什么的).这种漫画,经常 ...
- 一个canvas的demo
该demo放于tomcat下运行,否则出现跨域错误 <!DOCTYPE html> <html> <head> <meta charset="utf ...
- 如何开发一个简单的HTML5 Canvas 小游戏
原文:How to make a simple HTML5 Canvas game 想要快速上手HTML5 Canvas小游戏开发?下面通过一个例子来进行手把手教学.(如果你怀疑我的资历, A Wiz ...
随机推荐
- iOS开发之圆角指定 分类: ios技术 2015-05-25 16:26 191人阅读 评论(0) 收藏
如果需要将UIView的4个角全部都为圆角,做法相当简单,只需设置其Layer的cornerRadius属性即可(项目需要使用QuartzCore框架).而若要指定某几个角(小于4)为圆角而别的不变时 ...
- RTMP开发记录 测试服务器搭建篇
nginx-rtmp-module 安装 最近在做直播功能,为了方便调试,在本地搭建一个rtmp server吧~ 我的配置环境是Ubuntu12.04 64 安装编译环境所需库 sudo apt-g ...
- Bzoj1479: [Nerrc1997]Puncher打孔机
1479: [Nerrc1997]Puncher打孔机 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 22 Solved: 14[Submit][Sta ...
- UVa 412 - Pi
题目大意:给定一种估算Pi的方法:给出一系列随机数,从中任选两个数,这两个数的最大公约数不大于1(互质)的概率为6/(Pi*Pi),然后给出一系列数,据此估算Pi的值.直接模拟就好了. #includ ...
- discuz开发学习
2014年3月24日 10:36:10 遇到一个问题,discuz 缓存的样式,没有自动生成.后来去后台 进行操作才有效. 解决了之前的遇到的 首页没有套用样式的问题. 现在的问题是 模版的扩展图片 ...
- 谈谈线程同步Lock和unLock
Lock可以使用Condition进行线程之间的调度,它有更好的灵活性,而且在一个对象里面可以有多个Condition(即对象监视器),则线程可以注册在不同的Condition,从而可以 有选择性的调 ...
- Python3基础 内嵌函数 简单示例
镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...
- Java中间件:淘宝网系统高性能利器(转)
淘宝网是亚太最大的网络零售商圈,其知名度毋庸置疑,吸引着越来越多的消费者从街头移步这里,成为其忠实粉丝.如此多的用户和交易量,也意味着海量的信息处理,其背后的IT架构的稳定性.可靠性也显得尤为重要.那 ...
- AngularJS指令进阶 – ngModelController详解
AngularJS指令进阶 – ngModelController详解 在自定义Angular指令时,其中有一个叫做require的字段,这个字段的作用是用于指令之间的相互交流.举个简单的例子,假如我 ...
- Java 八大类型、String和 StringBuffer
1. 八大类型 类型 封装类 占字节 int; Integer; 4 short; Short; 2 byte; Byte; ...