在wp/silverlight/wpf也會經常看到控件模板。經常混淆的應該是DataTemplate和ControlTemplate,本篇文章就來談談兩件衣服DataTemplate和ControlTemplate的關系。本篇文章主要會以wpf控件為主,以便最后的源碼大家都可以打開。
一、ContentControl中的DataTemplate
在開始之前,我們先去看一下ContentControl的定義,無論在wp還是在wpf中其都有下面兩個屬性:
public object Content { get; set; }
public DataTemplate ContentTemplate { get; set; }
其特點是只能容納一個內容,內容類型是object類型,其中Button控件是我們大家比較熟悉且屬於ContentControl的類,下面我們看一下直接使用TextBlock作為其內容,Button會工作的很好。如下圖:
這個很正常,因為內容是object的嘛,那么下面我就使用另外一種筆刷作為其內容。看結果:
顯示內容成了筆刷轉化后的字符串。如果是在回頭看看DataTemplate的話,會發現其摘要是:
獲取或設置用於顯示 System.Windows.Controls.ContentControl 內容的數據模板。內容的數據模板也就是說內容以什么樣子表現出來。
下面我就讓button的內容-筆刷在一個圓上顯示出來。
以上我使用了button內容控件是實現了用圓形來展示button的內容。當然推而廣之,我可以使用任何內容控件,先設置其Content(該內容可以是任何復雜的內容),然后使用DataTemplate來表達Content的數據。下面我們就使用一個UserControl控件來實現一個同學的信息:定義一個Student類,然后初始化一個stu,設置其為UC的Content,然后羅列出Content中的數據。
由上面的兩個例子可以得出的結論是ContentControl中的DataTemplate是用來表示Content中的數據的,也就是說Content是DataTemplate的綁定的源,具體的表現形式是由DataTemplate決定的。
二、Control的ControlTemplate
在Control中,有個Template屬性,其摘要和返回結果如下:
// 摘要:
// 獲取或設置控件模板。
// 返回結果:
// 用於定義 System.Windows.Controls.Control 的外觀的模板。
public ControlTemplate Template { get; set; }
和DataTemplate不一樣的是:該控件定義外觀模板。我們還以Button為例子吧。上面返回結果說了是外觀的模板,那我想要一個圓角的Button,應該屬於外觀的范疇了,很快我想到了使用Border.下面就開工吧。我先弄個按鈕,給他寫上內容和加上背景顏色:
然后加上Template屬性,結果發現背景顏色和內容都沒有了。
如果是按照這樣的寫法,上面的結果可以看到Content沒有abc了。為了顯示出來abc我是不是可以在Border里面加個控件TextBlock,然后在 TextBlock上面寫上幾個字母,發現可以顯示了,但是如果是寫的不是abc,還是不能顯示abc,說明現在顯示的內容的決定於TextBlock,如果能有一種綁定的話多好呢,我就可以讓TextBlock顯示的和abc的一致了。有個TemplateBinding,使用時要在ControlTemplate標簽中使用Target。效果如下:
雖然效果實現了,但是有個很嚴重的問題是我們的Button的Content是object類型,Text是一個字符串類型。如果是button什么時間心情不好,Content屬性變成Image了,那是不是我要跟着“Button”受氣呢?為了不受氣,想一下有沒有使用於所有類型內容的容器呢?答案是肯定的。使用ContentPresenter。現在無論你是圖片還是文字,我都不用鳥你,都有ContentPresenter照着。下面亮出來他的樣子
ContentPresenter非常標准,他會為你自動匹配Target的Content和ContentTemplate。如果Button.Content有關的屬性(如FontSize,Foreground,FontFamily等)發生變化了,不用做任何更改ContentPresenter會幫我們處理的很好,但是如果是和Button本身有關的屬性,(如背景色等),需要顯式的調整。
三、DataTemplate和ControlTemplate聯系
在上面的例子中使用ContentPresenter時,會發現其也有一個ContentTemplate,是不是會猜出來是DataTemplate類型的,而且是在Template樹上"長着"。由此可以猜想ContentTemplate對應的DataTemplate是Template對應的ControlTemplate樹上的一棵子樹。為了證明這個事實,下面我還以Button來說明。
現在我分別在button里面使用文字,圖片,筆刷作為button的內容,然后添加一個容器來顯示Template的子樹,下面是xaml代碼:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.Column="0" Content="Click to Dump" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick" /> <Button Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"> <Image Source="Images/vs.png" Stretch="None" /> </Button> <Button Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"> <Button.Content> <RadialGradientBrush> <GradientStop Offset="0" Color="Blue" /> <GradientStop Offset="1" Color="AliceBlue" /> </RadialGradientBrush> </Button.Content> <Button.ContentTemplate> <DataTemplate> <Ellipse Width="100" Height="100" Fill="{Binding}" /> </DataTemplate> </Button.ContentTemplate> </Button> <ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" HorizontalScrollBarVisibility="Auto"> <StackPanel Name="stackPanel" /> </ScrollViewer> </Grid>
我可以根據可視樹的查找(VisualTreeHelper類提供的方法結合遞歸算法)來查看Button中的Template下面都有哪些子樹。下面代碼是后台代碼:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } void OnButtonClick(object sender, RoutedEventArgs args) { Button btn = sender as Button; stackPanel.Children.Clear(); DumpVisualTree(btn, 0); } void DumpVisualTree(DependencyObject parent, int indent) { TextBlock txtblk = new TextBlock(); txtblk.Text = String.Format("{0}{1}", new string(' ', 4 * indent), parent.GetType().Name); stackPanel.Children.Add(txtblk); int numChildren = VisualTreeHelper.GetChildrenCount(parent); for (int childIndex = 0; childIndex < numChildren; childIndex++) { DependencyObject child = VisualTreeHelper.GetChild(parent, childIndex); DumpVisualTree(child, indent + 1); } } }
分別點擊各個按鈕,可以看到各個按鈕的Template是怎么構造的,有個共同的特點可視樹都包含有ContentPresenter,這不正說明了DataTemplate被ContentPresenter替代掉了,說明的是DataTemplate生成的是ContentPresenter以下的樹(wp和silverlight中ContentPresenter以下的樹可能和wpf上面有些不一樣)。也驗證了DataTemplate是ControlTemplate的子樹的一部分。
四、總結
本文主要通過介紹DataTemplate和ControlTemplate,然后引入ContentPresenter,通過可視樹的幫助類VisualTreeHelper類查看控件所包含的模板內容,進而驗證了DataTemplate和ControlTemplate的關系。如果你覺得本文哪里有說的不對的地方,歡迎指正!感謝閱讀!
源碼下載:http://files.cnblogs.com/lzhp/TemlateDemo.zip