原文:
正确了解WPF中的TemplatedParent http://www.cnblogs.com/mgen/archive/2011/08/31/2160581.html(注: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}" />
</DataTemplate>
</ContentControl.ContentTemplate>
<!-- 邏輯孩子 -->
<Button Name="btn">按鈕</Button>
</ContentControl>
這個ContentControl的Visual 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
bd2
TemplatedParent: System.Windows.Controls.ContentPresenter
cp2
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>
這個TemplateBinding的資料源在哪裡?答案就是ContentControl中預設控件模闆裡的ContentPresenter,是以這裡資料模闆内的ContentPresenter的Content直接綁定到控件模闆中的ContentPresenter的Content屬性,當然這個僅僅為了做示例,實際上用Content=”{Binding}”也可以。