正確理解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}" />
</Border>
</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
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>
這個TemplateBinding的數據源在哪裏?答案就是ContentControl中默認控件模板裏的ContentPresenter,所以這裏數據模板內的ContentPresenter的Content直接綁定到控件模板中的ContentPresenter的Content屬性,當然這個僅僅為了做示例,實際上用Content=”{Binding}”也可以。
正確理解WPF中的TemplatedParent