A long time ago in a galaxy far, far away....微软在Silverlight Toolkit里提供了一个好用的VisualTreeExtensions,里面提供了一些查找VisualTree的扩展方法。在那个时候(2009年),VisualTreeExtensions对我来讲正好是个很棒的Linq和扩展方法的示例代码,比那时候我本身写的FindChildByName之类的方法好用一万倍,因此我印象深入。并且由于很实用,因此我一直在用这个类(即便是在WPF中),而此次我也把它添加到Kino.Wpf.Toolkit中,能够在 这里 查看源码。html
public static class VisualTreeExtensions { /// 获取 visual tree 上的祖先元素 public static IEnumerable<DependencyObject> GetVisualAncestors(this DependencyObject element) { } /// 获取 visual tree 上的祖先元素及自身 public static IEnumerable<DependencyObject> GetVisualAncestorsAndSelf(this DependencyObject element) { } /// 获取 visual tree 上的子元素 public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject element) { } /// 获取 visual tree 上的子元素及自身 public static IEnumerable<DependencyObject> GetVisualChildrenAndSelf(this DependencyObject element) { } /// 获取 visual tree 上的后代元素 public static IEnumerable<DependencyObject> GetVisualDescendants(this DependencyObject element) { } /// 获取 visual tree 上的后代元素及自身 public static IEnumerable<DependencyObject> GetVisualDescendantsAndSelf(this DependencyObject element) { } /// 获取 visual tree 上的同级别的兄弟元素 public static IEnumerable<DependencyObject> GetVisualSiblings(this DependencyObject element) { } /// 获取 visual tree 上的同级别的兄弟元素及自身. public static IEnumerable<DependencyObject> GetVisualSiblingsAndSelf(this DependencyObject element) { } }
VisualTreeExtensions封装了VisualTreeHelper
并提供了各类查询Visual Tree的方法,平常中我经常使用到的,在Wpf上也没问题的就是以上的功能。使用代码大体这样:git
foreach (var item in this.GetVisualDescendants().OfType<TextBlock>()) { }
VisualTreeExtensions虽然好用,但仍是有些问题须要注意。github
FrameworkElement在生成当前模板并构造Visual Tree时会调用OnApplyTemplate函数,但这时候最好不要使用VisualTreeExtensions去获取Visual Tree中的元素。所谓的最好,是由于WPF、Silverlight、UWP控件的生命周期有一些出入,我一时记不太清楚了,总之根据经验运行这个函数的时候可能Visual Tree尚未构建好,VisualTreeHelper获取不到子元素。不管个人记忆是否出错,正确的作法都是使用 GetTemplateChild 来获取ControlTemplate中的元素。windows
<StackPanel Margin="8"> <GroupBox Header="GroupBox" > <TextBox Margin="8" Text="FirstTextBox"/> </GroupBox> <TextBox Margin="8" Text="SecondTextBox" /> </StackPanel>
假设有如上的页面,执行下面这句代码:api
this.GetVisualDescendants().OfType<Control>().FirstOrDefault(c=>c.IsTabStop).Focus();
这段代码的意思是找到此页面第一个能够接受键盘焦点的控件并让它得到焦点。直觉上FirstTextBox是这个页面的第一个表单项,应该由它得到焦点,但GetVisualDescendants
的查找方法是广度优先,由于SecondTextBox比FirstTextBox深了一层,因此SecondTextBox得到了焦点。函数
Popup没有本身的Visual Tree,打开Popup的时候,它的Child和Window不在同一个Visual Tree中。以ComboBox为例,下面是ComboBox的ControlTemplate中的主要结构:工具
<Grid Name="templateRoot" SnapsToDevicePixels="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0" /> </Grid.ColumnDefinitions> <Popup Name="PART_Popup" AllowsTransparency="True" Margin="1" Placement="Bottom" Grid.ColumnSpan="2" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"> <theme:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding ComboBox.MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}"> ... </theme:SystemDropShadowChrome> </Popup> <ToggleButton Name="toggleButton"/> <ContentPresenter Name="contentPresenter"/> </Grid>
在实时可视化树视图中能够看到有两个VisualTree,而Popup甚至不在里面,只有一个叫PopupRoot的类。具体可参考 Popup 概述 这篇文档。ui
不过ComboBox的Popup在逻辑树中是存在的,若是ComboBoxItem想获取ComboBox的VisualTree的祖先元素,能够配合逻辑树查找。this
GetVisualAncestors
能够方便地查找各级祖先元素,一直查找到根元素,例如要找到根元素能够这样使用:spa
element.GetVisualAncestors().Last()
但若是元素不在Popup中,别忘了直接使用GetWindow更快捷:
Window.GetWindow(element)
不少控件库都封装了本身的查找VisualTree的工具类,下面是一些常见控件库的方案:
VisualTreeExtensions的代码很简单,我估计在UWP中也能使用,不过UWP已经在WindowsCommunityToolkit中提供了一个新的版本,只由于出于习惯,我还在使用Silverlight Toolkit的版本。并且Toolkit中的FindDescendantByName(this DependencyObject element, string name)
让我回忆起了我当年抛弃的FindChildByName
,一点都不优雅。
延续VisualTreeExtensions的习惯,多年来我都把扩展方法写在使用-Extensions
后缀命名的类里,不过我不记得有这方面的相关规范。
VisualTreeHelper Class (System.Windows.Media) _ Microsoft Docs
FrameworkElement.GetTemplateChild(String) Method (System.Windows) Microsoft Docs
VisualTreeExtensions.cs at master · DinoChan_Kino.Toolkit.Wpf
原文出处:https://www.cnblogs.com/dino623/p/VisualTreeExtensions.html