目录

在ControlTemplate中使用VisualTreeHelper和LogicalTreeHelper

目录

在TabControl中给TabItem定义如下的Template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
	<Border x:Name="mainBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" Background="{TemplateBinding Background}" Margin="0">
		<Border x:Name="innerBorder" BorderBrush="{StaticResource TabItem.Selected.Border}" BorderThickness="1,1,1,0" Background="{StaticResource TabItem.Selected.Background}" Margin="-1" Opacity="0"/>
	</Border>
	<DockPanel Margin="2,0">
	<Button Click="Button_Click" DockPanel.Dock="Right" Content="X" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="Blue"/>
	<ContentPresenter x:Name="contentPresenter" ContentSource="Header" Focusable="False" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"  RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
	</DockPanel>
</Grid>
</ControlTemplate>

实现的效果是给每一个TabItem添加关闭按钮:

/logicaltreehelper_visualtreehelper/vt1.png

现在需要实现关闭按钮的功能,那么则需要在后台Button_Click事件中通过sender找到类型为TabItem的parent。

然而却发现通过 FrameWorkElement.Parent 方法怎么也没法找到TabItem,当找到x:Name="templateRoot"的Grid时,再往上找,就是null.

问题的原因就在于FrameWorkElement.Parent是获取此元素的逻辑父级元素。也就是通过LogicalTreeHelper查找。在MSDN中备注了:

对于模板,模板的 Parent 最终将为 null。 若要忽略这一点并扩展到实际应用模板的逻辑树,请使用 TemplatedParent。

所以就简单来说,后台代码可以是:

1
2
3
4
5
6
private void Button_Click(object sender, RoutedEventArgs e)
{
	Button btn = sender as Button;
	TabItem item = btn.TemplatedParent as TabItem;
	//....
}

那么如果一定要使用一层一层查找Parent的方法,就需要用VisualTreeHelper.我们在下面的代码执行完Button btn = sender as Button;的地方加入一个断点。

1
2
3
4
5
private void Button_Click(object sender, RoutedEventArgs e)
{
	Button btn = sender as Button;
	//....
}

触发断点时将鼠标光标移到btn上,点击"放大镜",就可以打开WPF可视化工具。

/logicaltreehelper_visualtreehelper/vt2.png

在这里可以看到视觉树中可以看到TabItem.

/logicaltreehelper_visualtreehelper/vt3.png

那么使用VisualTree重写后台:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void Button_Click(object sender, RoutedEventArgs e)
{
			Button btn = sender as Button;
			DependencyObject parent = VisualTreeHelper.GetParent(btn);
			while (parent != null)
			{
				if (parent is TabItem)
				{
					break;
				}
				else
				{
					parent = VisualTreeHelper.GetParent(parent);
				}
			}
			if (parent != null)
			{
				TabItem item = parent as TabItem;
				TabControl tc = item.Parent as TabControl;

				tc.Items.Remove(item);
			}
			else
			{
				MessageBox.Show("没有找到TabItem");
			}
}

这样就能正确的找到TabItem了,如果替换VisualTreeHelper为LogicTreeHelper,则会弹出提示"没有找到TabItem".

其实VisualTreeHelper和LogicTreeHelper在ControlTemplate上有个交点,LogicTreeHelper不会跨过ControlTemplate,因为一个Control是一个逻辑元件,所以LogicTreeHelper只工作在在一个Control的ControlTemplate内部,当LogicTreeHelper获取控件的Parent到达ControlTemplate边界,则返回null。视觉树VisualTreeHelper则不同,它的层级关系是贯通的。不会受到ControlTemplate影响。如果要在ControlTemplate中使用相关操作,需要注意以上几点。