一.摘要
虽然我们平时几乎不会从该类派生,但要想了解视觉树就必须要了解Visual,Visual是一个基本抽象类,继承自DependencyObject.其是所有控件的基类.并提供了视觉树操作的基本方法.
二.提纲
- 视觉树是一棵树
- 遍历视觉树
- 内置Visual集合容器ContainerVisual
- 小结
视觉树是一棵树
这好像是一句废话,但也没有错.我们来看下Visual提供的一些基本的成员。
首先我们创立一个测试的对象
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); }
2.遍历视觉树
在调用AddVisualChild的时候,将会为两个Visual之间建立父子关系,子级知道父级,但父级却不知道有几个子级.所以很难遍历全部节点.需要把子节点给保存下来.Visual提供了两个成员用于视觉树的遍历,只要实现这两个成员就可以使用VisualTreeHelper进行遍历了.
下面我们就来实现这两个成员
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); } }
测试结果
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的呈现.那就下篇了.