2.2.5 ItemTemplate、ContentTemplate和DataTemplate
在理解ItemTemplate、ContentTemplate和DataTemplate的關系的之前,我們先來看看ContentControl類和ItemsControl類。ContentControl類是內容控件的基類,如Button, CheckBox,最明顯的特征就是這個控件有Content屬性,有Content屬性的系統控件都是ContentControl的子類。ItemsControl類是列表內容控件的基類,如ListBox,它和ContentControl類是類似的,只不過ContentControl類是單項的內容,ItemsControl是多項的內容。
那么所有繼承自ContentControl的內容控件的ContentTemplate屬性和所有繼承自ItemsControl的列表控件的ItemTemplate屬性,都是DataTemplate類型的,意思就是我們可以通過DataTemplate來定義ContentControl和ItemsControl的控件的UI效果和數據的顯示。
2.2.6 數據模板的使用
DataTemplate是一種可視化的數據模板,它強大的作用在於可以把數據通過綁定的方式展現到控件上。在上面的例子中,我們介紹了用DataTemplate去實現了UI控件的內容的顯示,那么其實DataTemplate最主要的作用並不是去取代ControlTemplate的樣式定義,而是通過數據綁定把數據的控件的數據源的信息展現到控件上。
下面我們還是通過一個Button的控件來看一下DataTemplate的數據綁定是如何發揮作用的。
代碼清單2-5:數據模板(源代碼:第2章\Examples_2_5)
(1)首先定義一個Person類表示是數據實體的類型,代碼如下:
public class Person { public string LastName { get; set; } public string FirstName { get; set; } }
(2)設計一個DataTemplate,並把這個DataTemplate作為一個資源來使用,這是和Style資源是一樣的道理,DataTemplate也可以作為公共的資源給多個控件去使用。那么這個模板的內容是使用StackPanel控件把Person對象的信息水平排列起來。
< Page.Resources> <DataTemplate x:Key="PersonNameDataTemplate"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding LastName}"/> <TextBlock Text=", "/> <TextBlock Text="{Binding FirstName}"/> </StackPanel> </DataTemplate> </ Page.Resources>
(3)創建一個Button控件,把ContentTemplate屬性和模板資源關聯起來。
<Button x:Name="singlePersonButton" ContentTemplate="{StaticResource PersonNameDataTemplate}"/>
(4)創建一個Person對象並且賦值給Button控件的Content屬性。
singlePersonButton.Content = new Person { FirstName = "lee", LastName = "Terry" };
最后我們可以看到按鈕的運行效果如圖2.14所示,DataTemplate可以把數據對象綁定起來來實現更加靈活的通用的強大的UI數據顯示效果。
圖2.14 數據模板綁定的按鈕
那么剛才的示例是DataTemplate在ContentControl類型的控件上的應用,那么下面我們再來看看DataTemplate在ItemsControl類型的控件上的實現,ContentControl和ItemsControl也是可以直接作為控件去使用的,如果我們並不需要Button或者ListBox這些控件的一些高級功能,就可以直接使用ContentControl或者ItemsControl控件。
(1)定義一個ItemsControl控件,把ItemTemplate屬性和模板資源關聯起來。
<ItemsControl x:Name="itemsControl" ItemTemplate="{StaticResource PersonNameDataTemplate}" />
(2)創建一個Person對象的集合並且賦值給ItemsControl控件的ItemsSource屬性。
Persons.Add(new Person { FirstName = "lee2", LastName = "Terry2" });
Persons.Add(new Person { FirstName = "lee3", LastName = "Terry3" });
Persons.Add(new Person { FirstName = "lee4", LastName = "Terry4" });
Persons.Add(new Person { FirstName = "lee5", LastName = "Terry5" });
itemsControl.ItemsSource = Persons;
這時候可以看到運行效果如圖2.15所示,ItemsControl可以把數據集合通過列表的形式展現出來,但是你會發現直接用ItemsControl實現的列表的功能非常有限,並且也不能滾動,接下來再結合一下ContentTemplate來進行完善這個列表的控件。
圖2.15 數據模板綁定的列表
(3)定義一個ItemsControl的樣式,其實就是自定義一個ControlTemplate的模板作為ItemsControl控件的模板來使用,那么這個模板就是一個內容的展現形式的模板。我們在ControlTemplate模板上定義了一個ScrollViewer控件然后里面再使用了一個StackPanel控件,最里面的是ItemsPresenter控件。列表的DataTemplate的顯示內容就是直接投影在ItemsPresenter控件上面的。我們對ScrollViewer控件和StackPanel控件都設置了不同的邊框顏色,這樣在運行的時候就可以很明顯地看出來控件之間的關系是怎樣的。
<Style x:Name="ItemsControlStyle" TargetType="ItemsControl"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ItemsControl"> <ScrollViewer BorderBrush="Red" BorderThickness="6"> <StackPanel Orientation="Horizontal" Background="Blue"> <Border BorderBrush="Yellow" BorderThickness="3"> <ItemsPresenter /> </Border> </StackPanel> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style>
(4)在ItemsControl上添加Style屬性為上面定義的樣式。
<ItemsControl x:Name="itemsControl" ItemTemplate="{StaticResource PersonNameDataTemplate}" Style="{StaticResource ItemsControlStyle}"/>
程序的運行效果如圖2.16所示。
圖2.16 列表控件的各個模塊
2.2.7 讀取和更換數據模板
對於系統的樣式,我們可以通過在C#代碼里面讀取出來然后再修改,實現動態更換主題的目的。那么技術總是相通的,對於控件的DataTemplate,我們一樣也可以通過C#代碼去讀取出來,然后動態地更換,實現更加豐富和靈活化的樣式展示方案。在C#代碼里面讀取和更換數據模板也是通過對ContentTemplate屬性進行讀取和賦值就可以了。
這種讀取和更換數據模板在列表的控件中會比較常見,比如我要實現一個這樣一個功能,通過一個列表展現出一批數據,用戶打擊某一條數據的時候,這條數據的樣式要發生改變,表示選取了這條數據,然后用戶可以取消這條數據的選擇也可以繼續選擇多條數據。那么這樣的功能在數據多選的情況下是非常普遍的功能來的。下面我們來實現這樣的一個功能。
代碼清單2-6:動態更換樣式(源代碼:第2章\Examples_2_6)
(1)定義3個DataTemplate資源,一個是非選中狀態,一個是選中狀態的,還有一個是默認的狀態,其實默認的狀態和非選中狀態是一樣的,但是因為默認的狀態的數據項樣式不能在C#里面再次調用。在兩個模板中都添加了Tap事件,用戶捕獲用戶的點擊事件。數據源集合與上一個例子一樣。
< Page.Resources> <!--選中數據項的樣式--> <DataTemplate x:Key="dataTemplateSelectKey" x:Name="dataTemplateSelectName"> <Grid Tapped="StackPanel_Tap_1" Background="Red"> <TextBlock Text="{Binding LastName}" FontSize="50" /> </Grid> </DataTemplate> <!--默認數據項的樣式,注意默認的數據項樣式不能在C#中再次調用--> <DataTemplate x:Key="dataTemplateDefaultKey" x:Name=" dataTemplateDefaultName"> <StackPanel Orientation="Horizontal" Tapped ="StackPanel_Tap_1"> <TextBlock Text="{Binding LastName}"/> <TextBlock Text=", "/> <TextBlock Text="{Binding FirstName}"/> </StackPanel> </DataTemplate> <!--非選中數據項的樣式--> <DataTemplate x:Key="dataTemplateNoSelectKey" x:Name="dataTemplateNoSelectName"> <StackPanel Orientation="Horizontal" Tapped ="StackPanel_Tap_1"> <TextBlock Text="{Binding LastName}"/> <TextBlock Text=", "/> <TextBlock Text="{Binding FirstName}"/> </StackPanel> </DataTemplate> </ Page.Resources> ……省略若干代碼 //創建ItemsControl控件來綁定列表的數據 <ItemsControl x:Name="listbox" ItemTemplate="{StaticResource dataTemplateDefaultKey }"/>
(2)處理點擊事件,判斷當前控件的模板和重新賦值模板。可以通過Name屬性訪問XAML中定義的DataTemplate。
private void StackPanel_Tap_1(object sender, TappedRoutedEventArgs e) { // 獲取ItemsControl對象的ItemContainerGenerator屬性 // 通過點擊的控件的DataContext判斷所綁定的數據對象 // 然后從ItemContainerGenerator里面獲取到當前的ContentPresenter對象 ContentPresenter myContentPresenter = (ContentPresenter)(listbox.ItemContainerGenerator.ContainerFromItem((sender as Panel).DataContext)); // 判斷數據模板是選中狀態的還是非選中狀態的,然后進行賦值 if (myContentPresenter.ContentTemplate.Equals(dataTemplateSelectName)) { //賦值非選中狀態的模板 myContentPresenter.ContentTemplate = dataTemplateNoSelectName; } else { //賦值選中狀態的模板 myContentPresenter.ContentTemplate = dataTemplateSelectName; } }
運行的效果如圖2.17所示,當我們點擊一下數據項的時候,字體會變大,背景會變成紅色,在點擊一次就會變成原來的樣子。
圖2.17 動態更換樣式
(3)在這里還要注意一點的是,如果使用的時ListBox控件而不是ItemsControl控件的時候,在獲取ContentPresenter對象的時候需要通過可視化樹去查找。代碼的實現如下所示:
private void StackPanel_Tap_1(object sender, TappedRoutedEventArgs e) { //獲取到的對象是ListBoxItem ListBoxItem myListBoxItem = (ListBoxItem)(listbox.ItemContainerGenerator.ContainerFromItem((sender as Panel).DataContext)); // 在ListBoxItem中查找ContentPresenter ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem); ……//省略若干代碼 } //查找可視化樹某個類型的元素 private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child != null && child is childItem) return (childItem)child; else { childItem childOfChild = FindVisualChild<childItem>(child); if (childOfChild != null) return childOfChild; } } return null; }
本文來源於《深入理解Windows Phone 8.1 UI控件編程》
源代碼下載:http://vdisk.weibo.com/s/zt_pyrfNHoezI
歡迎關注我的微博@WP林政
WP8.1技術交流群:372552293