[UWP]附加属性2:实现一个Canvas

简介: 原文:[UWP]附加属性2:实现一个Canvas5. 附加属性实践:自定义Canvas 附加属性在UWP中是一个十分重要的组成部分,很多功能都依赖于附加属性实现,典型的例子是常用的Grid和Canvas。
原文: [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自定义的附加属性

目录
相关文章
|
3月前
|
XML 数据格式
Andriod中为Dialog设置动画
Andriod中为Dialog设置动画
42 1
|
C++
Qt动态添加控件并设置大小位置等属性
Qt动态添加控件并设置大小位置等属性
1003 0
|
C#
WPF UserControl 的绑定事件、属性、附加属性
原文:WPF UserControl 的绑定事件、属性、附加属性 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Vblegend_2013/article/details/83477473 ...
2159 0
|
C#
WPF整理-为控件添加自定义附加属性
原文:WPF整理-为控件添加自定义附加属性 附加属性,大家都不陌生,最常见的是Canvas.Left/Canvas.Top,类似的也有Grid.Row/Grid.Column等附加属性。举个最常见的例子 需要说明的是并不是所有的附加属性都是元素放进去后才会有附加效果,上面的例子只是刚好是这种错觉的巧合情况,Grid.Row也属于这种巧合。
2121 0
|
C#
wpf 依赖属性和附加属性
原文:wpf 依赖属性和附加属性 1、依赖属性   解释:依赖属性是配合binding出现的产物,功能主要是配合binding。   作用:     一、当自定义usercontrol时,需要与宿主(父窗体)双向绑定值时,用到依赖属性。
1978 0
|
XML C# 数据格式
[UWP]为附加属性和依赖属性自定义代码段(兼容UWP和WPF)
原文:[UWP]为附加属性和依赖属性自定义代码段(兼容UWP和WPF) 1. 前言 之前介绍过依赖属性和附加属性的代码段,这两个代码段我用了很多年,一直都帮了我很多。不过这两个代码段我也多年没修改过,Resharper老是提示我生成的代码可以修改,它这么有诚意,这次就只好从了它,顺便简单介绍下怎么自定义代码段。
759 0
|
C#
WPF xaml中列表依赖属性的定义
原文:WPF xaml中列表依赖属性的定义 列表内容属性 如上图,是一个列表标题排序控件,我们需要定义一个标题列表,从而让调用方可以自由的设置标题信息。 在自定义控件时,会遇到列表依赖属性,那么该如何定义呢? 下面是错误的定义方式: 1 /// 2 /// 标识 的依赖项属性。
1102 0
|
C#
WPF XAML 资源样式模板属性存放位置
原文:WPF XAML 资源样式模板属性存放位置 WPF的XAML 资源申明 类似HTML。 整体来说分3种1.行类资源样式属性 1.1 行内属性 1.2 行内样式 模板(没多大意义) ...
1140 0
|
C# 数据安全/隐私保护
WPF 使用附加属性增加控件属性
原文:WPF 使用附加属性增加控件属性 使用附加属性增加控件属性,使得这个附加属性在使用的时候没有局限性,可以在任何的控件中使用它来增加所需要的属性,使得控件的属性使用起来非常灵活   一、自定义附加属性 1 2 3 4 5 6 7 8 9 10 11 ...
892 0
|
C#
WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性
原文:WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性 如果你要自定义一个图片按钮控件,那么如何在主窗体绑定这个控件上图片的Source呢? 我向大家介绍一个用 依赖属性(DependencyProperty) 实现的方法。
2489 0