可视化树和逻辑树
我们先来理解一下什么是可视化树和逻辑树。
可视化树:包含最初指定的大多数元素(在XAML或.cs中)以及控件模板中的元素。
通俗点来讲,就是整个元素的构成树,从最上面的结点到最后一个结点(包括控件模板)。
逻辑树:是可视化树的一个子集,它省略了控件模板中的元素。
通俗点来讲,就是不包含控件模板的可视化树
以上的解释仅仅用于简单理解这两个概念以及区别,完整的解释请参考文末的链接,MSDN上的文档将会更准确。
下面我们创建一个Window来演示,XAML如下:
1 <Window> 3 <Grid Name="grid" Grid.Column="1"> 4 <Button HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,80,0" Grid.Row="1" Content="获取可视化树" Click="GetVisualTree_Click"></Button> 5 <Button HorizontalAlignment="Right" VerticalAlignment="Center" Content="获取逻辑树" Grid.Row="1" Click="GetLogicalTree_Click"></Button> 6 </Grid> 7 </Window>
在WPF中提供了VisualTreeHelper 和 LogicalTreeHelper 两个类来对可视化树和逻辑树进行操作。
使用VisualTreeHelper来获取Grid的可视化树
1 public void PrintVisualTree(DependencyObject parent, int level) 2 { 3 string typeName = parent.GetType().FullName ?? parent.GetType().Name; 4 string name = (string)(parent.GetValue(FrameworkElement.NameProperty) ?? ""); 5 AppendText(" ".Substring(0, level * 2)); 6 AppendText($"{typeName}:",true); 7 AppendText($" {name}"); 8 AppendText(Environment.NewLine); 9 for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i) 10 { 11 DependencyObject child = VisualTreeHelper.GetChild(parent, i); 12 PrintVisualTree(child, level + 1); 13 } 14 }
输出结果如下:
可以看到Grid的视觉树不仅打印了两个Button,还打印了Button的控件模板。
Button控件的控件模板如下:
1 <Border TextBlock.Foreground="{TemplateBinding Foreground}" 2 x:Name="Border" 3 CornerRadius="2" 4 BorderThickness="1"> 5 <ContentPresenter Margin="2" 6 HorizontalAlignment="Center" 7 VerticalAlignment="Center" 8 RecognizesAccessKey="True" /> 9 </Border>
使用LogicalTreeHelper来获取Grid的逻辑树
1 public void PrintLogicalTree(object parent, int level) 2 { 3 var parentObj = parent as DependencyObject; 4 var typeName = parent.GetType().FullName; 5 var name = ""; 6 if (parentObj != null) 7 { 8 name = (string)(parentObj.GetValue(FrameworkElement.NameProperty) ?? ""); 9 } 10 else 11 { 12 name = parent.ToString(); 13 } 14 15 AppendText(GetIndentString().Substring(0, level * 2)); 16 AppendText($"{typeName}:", true); 17 AppendText($" {name}"); 18 AppendText(Environment.NewLine); 19 20 if (parentObj == null) 21 return; 22 23 var children = LogicalTreeHelper.GetChildren(parentObj); 24 foreach (object child in children) 25 { 26 PrintLogicalTree(child, level + 1); 27 } 28 }
运行结果如下:
可以看到Grid的逻辑树只显示到了Button的内容这一层。
可视化树的用途
在上面的Button模板中,我们定义了一个Border,并命名为Border,
如果我们想查找这个控件,并为之添加事件处理程序或设置一些属性,都可以通过可视化树来实现。
首先我们定义一个遍历可视化树的方法
1 public Visual EnumVisual(Visual myVisual,string controlName) 2 { 3 for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++) 4 { 5 6 Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i); 7 var nameObj = childVisual.GetValue(NameProperty); 8 9 if (nameObj != null && nameObj.ToString() == controlName) 10 return childVisual; 11 12 EnumVisual(childVisual,controlName); 13 } 14 15 return null; 16 }
然后我们查找Border,并设置它的CornerRadius属性
1 //查找控件模板中的名称为Border的控件 2 var border = EnumVisual(this.btn1, "Border"); 3 if (border != null) 4 { 5 var borderObj = border as Border; 6 7 if (borderObj != null) 8 { 9 borderObj.CornerRadius = new CornerRadius(10); 10 } 11 }
运行效果
实际上我们还可以通过FrameworkTemplate.FindName方法进行查找,使用方法如下:
1 object findObj = this.btn1.Template.FindName("Border",this.btn1);
FrameworkTemplate.FindName内部没有直接调用VisualTreeHelper,它是利用缓存查找的。
逻辑树的用途
借助逻辑树,内容模型可以方便地循环访问其可能的子对象,从而实现扩展。
FrameworkElement提供了一个FindName的方法,可以通过名称查找子对象
1 public object FindName(string name);
它内部的实现实际就是借助的逻辑树
1 internal object FindName(string name, out DependencyObject scopeOwner) 2 { 3 INameScope scope = FindScope(this, out scopeOwner); 4 if (scope != null) 5 { 6 return scope.FindName(name); 7 } 8 return null; 9 }
1 internal static INameScope FindScope(DependencyObject d, out DependencyObject scopeOwner) 2 { 3 while (d != null) 4 { 5 INameScope scope = NameScope.NameScopeFromObject(d); 6 if (scope != null) 7 { 8 scopeOwner = d; 9 return scope; 10 } 11 DependencyObject parent = LogicalTreeHelper.GetParent(d); 12 d = (parent != null) ? parent : Helper.FindMentor(d.InheritanceContext); 13 } 14 scopeOwner = null; 15 return null; 16 }
示例代码
参考链接:
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/trees-in-wpf?view=netframeworkdesktop-4.8