zoukankan      html  css  js  c++  java
  • [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自定义的附加属性

  • 相关阅读:
    Spring实现AOP
    js Form表单转json格式,及后台接收(多种方法)
    Java 网络编程
    分布式系统学习
    java消息中间件
    oracle Clob类型转换成String类型
    Oracle的CLOB大数据字段类型
    oracle wm_concat函数 列转行 分组函数
    Oracle trunc函数使用
    ajax异步提交文件
  • 原文地址:https://www.cnblogs.com/dino623/p/6359729.html
Copyright © 2011-2022 走看看