Visual Tree是Logical Tree节点扩充后的的产物

时间:2021-09-11 08:26:39

(注:Logical Tree中文称为逻辑树,Visual Tree中文称为可视化树或者视觉树,由于名称不是很统一,文中统一用英文名称代表两个观点,况且VisualTreeHelper和LogicalTreeHelper也是WPF*给的类名称)

众所周知WPF中的Logical Tree是逻辑上界说的元素条理树,而实际上显示在屏幕上的元素条理树是Visual Tree,Visual Tree是Logical Tree节点扩充后的的产物。因此从Visual Tree的角度上看(Visual Tree固然是完整的一个),Logical Tree被支解成一段一段的,而这些段与段的连接点,就是和TemplatedParent有关。

这个观点在WPF类模型中是FrameworkElement.TemplatedParent属性。WPF中的模板(数据模板和控件模板)都可以扩展Logical Tree,那么模板所修饰的东西就是模板中元素的TemplatedParent,此时模板元素和修饰东西城市呈此刻Visual Tree中,但模板元素必定不属于被修饰元素的Logical Tree,但是模板有本身的Logical Tree,,两个Logical Tree是分隔的,但是通过TemplatedParent,两者之间又有联系。

说再多不如实例形象,来看下面示例代码:

这是一个简单的ContentControl,它的Content是一个按钮,然后界说了控件模板和数据模板,代码中一些关键元素有Name属性,我们在后续讨论就以Name属性的值来引用这些元素。

<ContentControl Name="contentControl">

<!-- 控件模板 -->

<ContentControl.Template>

<ControlTemplate TargetType="ContentControl">

<Border Name="bd1">

<ContentPresenter Name="cp1" ContentSource="Content"/>

</Border>

</ControlTemplate>

</ContentControl.Template>

<!-- 数据模板 -->

<ContentControl.ContentTemplate>

<DataTemplate>

<Border Name="bd2">

<ContentPresenter Name="cp2" Content="{Binding}" />

</Border>

</DataTemplate>

</ContentControl.ContentTemplate>

<!-- 逻辑孩子 -->

<Button Name="btn">按钮</Button>

</ContentControl>

这个ContentControl的Visual Tree如下图:

Visual Tree是Logical Tree节点扩充后的的产物

图中不异颜色的节点代表它们属于同一个Logical Tree,可以看出来,整个Visual Tree分成多个Logical Tree,而这些Logical Tree是分隔的,好比上面代码中的两个Border(名称是bd1和bd2),它们的Parent属性的值都是null,即没有逻辑父节点。但是这些逻辑树通过TemplatedParent是互相有联系的。好比控件模板中的元素的TemplatedParent指代最上方的ContentControl,而数据模板元素的TemplatedParent则是控件模板内的ContentPresenter元素。

通过代码也可以验证这些:(bd1, bd2, cp1, cp2分袂代表控件模板和数据模板中的Border和ContentPresenter)

private void Button_Click(object sender, RoutedEventArgs e)

{

var bd1 = (Border)contentControl.Template.FindName("bd1", contentControl);

var cp1 = (ContentPresenter)contentControl.Template.FindName("cp1", contentControl);

var bd2 = (Border)contentControl.ContentTemplate.FindName("bd2", cp1);

var cp2 = (ContentPresenter)contentControl.ContentTemplate.FindName("cp2", cp1);

PrintInfo(bd1, cp1, bd2, cp2, btn);

}

void PrintInfo(params FrameworkElement[] eles)

{

string s = "";

foreach (var ele in eles)

s += String.Format("{2}\r\nParent: {0}\r\nTemplatedParent: {1}\r\n\r\n", ele.Parent, ele.TemplatedParent, ele.Name);

MessageBox.Show(s);

}

输出信息:(冒号后没有值则代表null)

bd1

Parent:

TemplatedParent: System.Windows.Controls.ContentControl: 按钮

cp1

Parent: System.Windows.Controls.Border

TemplatedParent: System.Windows.Controls.ContentControl: 按钮

bd2

Parent:

TemplatedParent: System.Windows.Controls.ContentPresenter

cp2

Parent: System.Windows.Controls.Border

TemplatedParent: System.Windows.Controls.ContentPresenter

btn

Parent: System.Windows.Controls.ContentControl: 按钮

TemplatedParent:

最后还有一个btn,指代ContentControl中的内容按钮,它属于主干逻辑树,因此Parent是ContentControl,同时它也不属于任何模板,不存在修饰东西,因此TemplatedParent为null

此外WPF数据绑定Binding类还撑持RelativeSource东西,这个RelativeSource类的Mode属性有一个TemplatedParent值,这个值就是代表数据绑定会将数据源作为,同时WPF中的TemplateBinding符号扩展可以便利界说此类绑定,此外TemplateBinding的绑定模式是OneWay。

了解了TemplatedParent,使用TemplateBinding也就非常灵活了,一般情况下TemplateBinding使用在界说控件模板下,但是在数据模板中也可以使用,好比下面这个例子:

<ContentControl>

<Button>Content</Button>

<ContentControl.ContentTemplate>

<DataTemplate>

<ContentPresenter Content="{TemplateBinding Content}" />

</DataTemplate>

</ContentControl.ContentTemplate>

</ContentControl>