• WPF的逻辑树与视觉树(2)Visual容器


    一.摘要

    虽然我们平时几乎不会从该类派生,但要想了解视觉树就必须要了解Visual,Visual是一个基本抽象类,继承自DependencyObject.其是所有控件的基类.并提供了视觉树操作的基本方法.

    二.提纲

    1. 视觉树是一棵树
    2. 遍历视觉树
    3. 内置Visual集合容器ContainerVisual
    4. 小结

    视觉树是一棵树

    这好像是一句废话,但也没有错微笑.我们来看下Visual提供的一些基本的成员。

    image

    首先我们创立一个测试的对象

    public class DefaultVisual : Visual
        {
            public string Key { get; set; }
    
            protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
            {
                Console.WriteLine(this.Key + " ChildrenChanged");
                if (visualAdded != null) Console.WriteLine((visualAdded as DefaultVisual).Key+" Added");
                if (visualRemoved != null) Console.WriteLine((visualRemoved as DefaultVisual).Key+"Removed");
                base.OnVisualChildrenChanged(visualAdded, visualRemoved);
            }
    
            protected override void OnVisualParentChanged(DependencyObject oldParent)
            {
                Console.WriteLine(this.Key + " ParentChanged");
                if (oldParent != null) Console.WriteLine((oldParent as DefaultVisual).Key);
                base.OnVisualParentChanged(oldParent);
            }
        }
    测试代码
    public static void Test()
    {
        var test1 = new DefaultVisual();
        test1.Key = "test1";
        var test2 = new DefaultVisual();
        test2.Key = "test2";
        test1.AddVisualChild(test2);
        var test3 = new DefaultVisual();
        test3.Key = "test3";
        test2.AddVisualChild(test3);
        var test4 = new DefaultVisual();
        test4.Key = "test4";
        test1.AddVisualChild(test4);
        test1.RemoveVisualChild(test4);
    }

    结果
    image

    2.遍历视觉树

    在调用AddVisualChild的时候,将会为两个Visual之间建立父子关系,子级知道父级,但父级却不知道有几个子级.所以很难遍历全部节点.需要把子节点给保存下来.Visual提供了两个成员用于视觉树的遍历,只要实现这两个成员就可以使用VisualTreeHelper进行遍历了.
    image
    下面我们就来实现这两个成员

    3.Visual容器

    Visual本身具备一些功能,同时也可以充当容器。
    在实际情况下,容器分为两种,单容器和集合容器.比如Border就是一个单容器,其内部只可以放一个元素.Panel是一个集合容器.可以放多个元素.

    单容器实现

    public class SigletonVisual : DefaultVisual
    {
        public Visual _child;
        public Visual Child
        {
            get
            {
                return _child;
            }
            set
            {
                this.RemoveVisualChild(_child);
                this.AddVisualChild(value);
                _child = value;
            }
        }
    
        protected override Visual GetVisualChild(int index)
        {
            return _child;
        }
    
        protected override int VisualChildrenCount
        {
            get
            {
                if (this._child != null)
                {
                    return 1;
                }
                return 0;
            }
        }
    }

    集合容器实现

    public class PanelVisual : DefaultVisual
    {
        public List<Visual> Visuals { get; set; }
    
        public PanelVisual()
        {
            Visuals = new List<Visual>(5);
        }
    
        public void Add(Visual visual)
        {
            Visuals.Add(visual);
            this.AddVisualChild(visual);
        }
    
        protected override Visual GetVisualChild(int index)
        {
            return Visuals[index];
        }
    
        protected override int VisualChildrenCount
        {
            get
            {
                return Visuals.Count;
            }
        }
    }

    遍历测试

    void Test()
    {
        var test1 = new PanelVisual();
        test1.Key = "test1";
        var test2 = new PanelVisual();
        test2.Key = "test2";
        test1.Add(test2);
        var test3 = new PanelVisual();
        test3.Key = "test3";
        test2.Add(test3);
        var test4 = new PanelVisual();
        test4.Key = "test4";
        test1.Add(test4);
        PrintVisualTree(0, test1);
    }
    
    public void PrintVisualTree(int depth, PanelVisual obj)
    {
        Console.WriteLine(new string(' ', depth) + obj.Key);
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i) as PanelVisual);
        }
    }

    测试结果

    image

    3.内置Visual集合容器ContainerVisual

    其实我们不用这么复杂,WPF内置类ContainerVisual已经默认实现了Visual集合容器,
    ContainerVisual内部采用VisualCollection集合来维护视觉树,所以当我们添加Visual的时候,不需要调用AddVisualChild方法,而是应该调用VisualCollection的Add和Remove等方法

    如下测试

    public class TestVisual : ContainerVisual
    {
        public static void Test2()
        {
            var test1 = new TestVisual();
            test1.Key = "test1";
            var test2 = new TestVisual();
            test2.Key = "test2";
            test1.Children.Add(test2);
            var test3 = new TestVisual();
            test3.Key = "test3";
            test2.Children.Add(test3);
            var test4 = new TestVisual();
            test4.Key = "test4";
            test1.Children.Add(test4);
            PrintVisualTree(0, test1);
        }
    
        public static void PrintVisualTree(int depth, TestVisual obj)
        {
            Console.WriteLine(new string(' ', depth) + obj.Key);
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i) as TestVisual);
            }
        }
    
        public string Key { get; set; }
    }


    测试结果是一样的,但我们就可以省却手动实现VisualChildrenCount和GetVisualChild这两个成员了.
    如果不从ContainerVisual 继承又想简单的维护Visual的话,可以使用VisualCollection来维护.

    4.小结

    这篇讲到了Visual的基本功能,Visual本身具备父子级关系的功能,但默认没有容器,需要我们自己实现.内置的ContainerVisual 使用VisualCollection实现了一个Visual容器功能.有了容器才能遍历整个视觉树,对Visual进行一些交互.
    可能到这里却还没有看到具体UI的呈现.那就下篇了.

  • 相关阅读:
    【Java】使用记事本运行第一个Java程序
    构建自己的PHP框架(日志)
    构建自己的PHP框架(Twig模板引擎)
    使用openssl工具生成密钥
    构建自己的PHP框架(Redis)
    构建自己的PHP框架(邮件发送)
    树莓记录
    两张图证明 WolframAlpha 的强大
    树莓3B+_Raspbian 源使用帮助
    树莓3B+_安装vim
  • 原文地址:https://www.cnblogs.com/Clingingboy/p/1794211.html
Copyright © 2020-2023  润新知