1. 程式人生 > 其它 >WPF Template模版之DataTemplate與ControlTemplate【一】

WPF Template模版之DataTemplate與ControlTemplate【一】

參考網址:https://blog.csdn.net/aoshilang2249/article/details/45127723

WPF系統不但支援傳統的Winfrom程式設計的使用者介面和使用者體驗設計,更支援使用專門的設計工具Blend進行專業設計,同時還推出了以模板為核心的新一代設計理念。

1. 模板的內涵
作為表現形式,每個控制元件都是為了實現某種使用者操作演算法和直觀顯示某種資料而生,一個控制元件看上去是什麼樣子由它的“演算法內容”和“資料內容"決定,這就是內容決定形式,這裡,我們引入兩個概念:

控制元件的演算法內容:控制元件能展示哪些資料、具有哪些方法、能響應哪些操作、能激發什麼事件,簡而言之就是控制元件的功能,它們是一組相關的演算法邏輯。
控制元件的資料內容:控制元件具體展示的資料是什麼。


以往的GUI開發技術(ASP.NET+Winform)中,控制元件內部邏輯和資料是固定的,程式設計師不能改變;對於控制元件的外觀,程式設計師能做的改變也非常的有限,一般也就是設定控制元件的屬性,想改變控制元件的內部結構是不可能的。如果想擴充套件一個控制元件的功能或者更改器外觀讓其更適應業務邏輯,哪怕只是一丁點的改變,也需要建立控制元件的子類或者建立使用者控制元件。造成這個局面的根本原因是資料和演算法的“形式”和“內容”耦合的太緊了。


在WPF中,通過引入模板,微軟將資料和演算法的內容與形式解耦了。WPF中的Template分為兩大類:
ControlTemplate:是演算法內容的表現形式,一個控制元件怎麼組織其內部結構才能讓它更符合業務邏輯、讓使用者操作起來更舒服就是由它來控制的。它決定了控制元件“長成什麼樣子”,並讓程式設計師有機會在控制元件原有的內部邏輯基礎上擴充套件自己的邏輯。
DataTemplate:是資料內容的展示方式,一條資料顯示成什麼樣子,是簡單的文字還是直觀的圖形就由它來決定了。


Template就是資料的外衣-----ControlTemplate是控制元件的外衣,DataTemplate是資料的外衣。

2. 資料的外衣DataTemplate
WPF不但支援UserControl還支援DataTemplate為資料形成檢視。不要以為DataTemplate有多難!從UserControl升級到DataTemplate一般就是複製,貼上一下再改幾個字元的事兒。

DataTemplate常用的地方有三處,分別是:
ContentControl的ContentTemplate屬性,相當於給ContentControl的內容穿衣服。
ItemsControl的ItemTemplate,相當於給ItemControl的資料條目穿衣服。
GridViewColumn的CellTempldate屬性,相當於給GridViewColumn的資料條目穿衣服。


事件驅動是控制元件和控制元件之間溝通或者說是形式和形式之間的溝通,資料驅動則是資料與控制元件之間的溝通,是內容決定形式。使用DataTemplate就可以方便的把事件驅動模式轉換為資料驅動模式。

讓我們用一個例子體現DataTemplate的使用。例子實現的需求是這樣的:有一列汽車資料,這列資料顯示在ListBox裡面,要求ListBox的條目顯示汽車的廠商圖示和簡要引數,單擊某個條目後在窗體的詳細內容區顯示汽車的圖片和詳細引數。廠商的Logo和汽車的照片都是要用到的,所以先在專案中建立資源管理目錄並把圖片新增進來。Logo檔名與廠商的名稱一致,照片的名稱則與車名一致。組織結構如圖:


建立Car資料型別:

/// <summary>
/// Car資料型別 -- 必須定義成屬性{ get; set; }
/// </summary>
public class Car
{
public string Name { get; set; }
public string ImagePath { get; set; }
public string Automarker { get; set; }
public string Year { get; set; }
}
汽車廠商和名稱不能直接拿來作為圖片路徑,這時就要使用Converter:

/// <summary>
/// 路徑轉圖片
/// </summary>
public class PathToImage:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string url = (string)value;
return(new BitmapImage(new Uri(url, UriKind.Relative)));
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML中新增條目模版:

<DataTemplate x:Key="_carListItemViewTemplate">
<Grid Margin="2">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Automarker, Converter={StaticResource _path2Image}}" Grid.RowSpan="3"
Width="64" Height="64"></Image>
<StackPanel Margin="5,10">
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding Year}" FontSize="14"></TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
XAML中新增顯示詳細資訊的模版:
<DataTemplate x:Key="_carDetailViewTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
<StackPanel Margin="5">
<Image Width="400" Height="250" Source="{Binding Path=ImagePath, Converter={StaticResource _path2Image}}"></Image>
<StackPanel Orientation="Horizontal" Margin="5, 0">
<TextBlock Text="Name:" FontWeight="Bold" FontSize="20"></TextBlock>
<TextBlock Text="{Binding Path=Name}" FontSize="20" Margin="5, 0"></TextBlock>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
完整的XAML程式碼:
<Window x:Class="WpfApplication11.wnd112"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication11"
Title="DataTemplate" Height="350" Width="623">
<Window.Resources>
<!--Convert-->
<local:PathToImage x:Key="_path2Image"></local:PathToImage>
<!--DataTemplate for Detail View -->
<DataTemplate x:Key="_carDetailViewTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
<StackPanel Margin="5">
<Image Width="400" Height="250" Source="{Binding Path=ImagePath, Converter={StaticResource _path2Image}}"></Image>
<StackPanel Orientation="Horizontal" Margin="5, 0">
<TextBlock Text="Name:" FontWeight="Bold" FontSize="20"></TextBlock>
<TextBlock Text="{Binding Path=Name}" FontSize="20" Margin="5, 0"></TextBlock>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
<!--DataTemplate for Item View -->
<DataTemplate x:Key="_carListItemViewTemplate">
<Grid Margin="2">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Automarker, Converter={StaticResource _path2Image}}" Grid.RowSpan="3"
Width="64" Height="64"></Image>
<StackPanel Margin="5,10">
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding Year}" FontSize="14"></TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<!---Window Content-->
<StackPanel Orientation="Horizontal" Margin="5">
<UserControl ContentTemplate="{StaticResource _carDetailViewTemplate}" Content="{Binding Path=SelectedItem, ElementName=_listBoxCars}"></UserControl>
<ListBox x:Name="_listBoxCars" Width="180" Margin="5,0" ItemTemplate="{StaticResource _carListItemViewTemplate}"></ListBox>
</StackPanel>
</Window>

程式碼對於初學者來說有點長但是結構非常簡單。其中最重要的有兩句:
ContentTemplate="{StaticResource _carDatialViewTemplate}",相當於給一個普通的UserControl穿上了一件外衣、讓Car資料以圖文並茂的方式展現出來。這件外衣就是x:Key="_carDatialViewTemplate"標記的DataTemplate資源。
ItemTemplate="{StaticResource _listBoxCars}",把每一件資料的外衣交給ListBox,當ListBox的ItemSource被賦值的時候,ListBox就會為每個條目穿上這件外衣。這件外衣是以x:Key="_listBoxCars"標記的DataTemplate資源。
因為不再使用事件驅動,而且為資料穿衣服的事也已經自動完成,所以後臺的C#程式碼就非常的簡單。窗體的C#程式碼就只剩下這些:

/// <summary>
/// wnd112.xaml 的互動邏輯
/// </summary>
public partial class wnd112 : Window
{
List<Car> _carList;
public wnd112()
{
InitializeComponent();

_carList = new List<Car>()
{
new Car(){Name = "Aodi1", ImagePath=@"/Resources/Images/Aodi.jpg", Automarker=@"/Resources/Images/01077_1.png", Year="1990"},
new Car(){Name = "Aodi2", ImagePath=@"/Resources/Images/Aodi.png", Automarker=@"/Resources/Images/01077_1.png", Year="2001"},
};

_listBoxCars.ItemsSource = _carList;
}
}
執行程式,效果如下圖:


3. 控制元件的外衣ControlTemplate
每每提到ControlTemplate我都會想到“披著羊皮的狼”這句話-----披上羊皮之後,雖然看上去像只羊,但其行為仍然是匹狼。狼的行為指的是它能吃別的動物、對著滿月嚎叫等事情,控制元件也有自己的行為,比如顯示資料、執行方法、激發事件等。控制元件的行為要靠程式設計邏輯來實現,所以也可以把控制元件的行為稱為控制元件的演算法內容。舉個例子,WPF中的CheckBox與其基類ToggleButton的功能幾乎完全一樣,但外觀差別上卻非常的大,這就是更換ControlTemplate的結果。經過更換ControlTemplate,我們不但可以製作披著CheckBox外衣的ToggleButton,還能製作披著溫度計外衣的ProgressBar控制元件。
注意:
實際專案中,ControlTemplate主要有兩大用武之地:
通過更換ControlTemplate來更換控制元件的外觀,使之具有更優的使用者體驗和外觀。
藉助ControlTemplate,程式設計師和設計師可以並行工作,程式設計師可以使用WPF標準控制元件進行程式設計,等設計師的工作完成之後,只需要把新的ControlTemplate應用的程式中即可。
ItemsControl具有一個名為ItemsPanel的屬性,它的資料型別是ItemsPanelTemplate。ItemsPanelTemplate也是一種控制元件Template,它的作用是可以讓程式設計師可以控制ItemsControl的條目容器。

舉例而言,在我們的印象中ListBox中的條目都是至上而下排列的,如果客戶要求我們做一個水平排列的ListBox怎麼辦呢?WPF之前,我們只能重寫控制元件比較底層的方法和屬性,而現在我們只需要調整ListBox的ItemsPanel屬性。

<Window x:Class="WpfApplication11.wnd1132"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="wnd1132" Height="80" Width="300">
<Grid>
<ListBox>
<!--條目容器-->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<!--條目元素-->
<TextBlock Text="Allan"></TextBlock>
<TextBlock Text="Allan2"></TextBlock>
<TextBlock Text="Allan3"></TextBlock>
<TextBlock Text="Allan4"></TextBlock>
</ListBox>
</Grid>
</Window>
條目就會包裝在一個水平排列的StackPanel中,從而橫向排列,如下圖所示: