在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添加关闭按钮:
现在需要实现关闭按钮的功能,那么则需要在后台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中使用相关操作,需要注意以上几点。