天天看點

正确了解WPF中的TemplatedParent

原文:

正确了解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如下圖:

正确了解WPF中的TemplatedParent

圖中相同顔色的節點代表它們屬于同一個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}”也可以。