WPF中UserControl和DataTemplate
最新更新: http://denghejun.github.io
前言
前言總是留給我說一些無關主題的言論,WPF作為全新Microsoft桌面或web應用程序顯示技術框架,
從08年開始,一直到現在,我也是在工作第一年后嘗試去了解。且在網上也有很多對于該技術框架的
評論,有好也有壞的,有一部分同學說WPF只是在剛出來的時候才火,后邊就落寞了,且現在國內外
通過WPF做出來的大型應用也沒多少;另外一部分同學則認為,技術不論好壞,存在即合理,學習WPF
一定不會虧。不知道你們是怎么想的。我是覺得,本身對技術方面比較感興趣,有時候會有自己的想法
來做一些東西,所以學習它會讓我在想要做些東西的時候能有可用的技術,或是當別人談及此事的時候,
至少我還知道。
UserControl
做過web開發或是winform的同學都知道,UserControl即是用戶控件,是程序員根據需要自定義的一組
控件集合。WPF中當然也有,相比起來,更為簡單,如下將會是一個簡單的定義UserControl和使用的例子。

大概要實現的UI是這樣子的,右邊的是一組在ListBox內的CarItem集合,點擊后在左邊顯示對應大圖。
首先我們定義Car數據結構:
1 // 簡單起見,就一個Name屬性 2 public class MCar 3 { 4 public string Name { get; set; } 5 }
接下來,設計右邊的ItemTemplate,使用UserControl的方式,以下依次是UserControl的XAML和后臺邏輯。
<UserControl x:Class="WPFUserControl.CarsView.MainForm.CarItem" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <StackPanel> <Grid> <Grid.RowDefinitions> <RowDefinition ></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="60"></ColumnDefinition> <ColumnDefinition Width="1*"></ColumnDefinition> </Grid.ColumnDefinitions> <Image Grid.Row="0" Grid.Column="0" x:Name="imgLogo" ></Image> <TextBlock Grid.Row="0" Grid.Column="1" x:Name="tbName"></TextBlock> </Grid> </StackPanel> </UserControl>
1 /// <summary> 2 /// CarItem.xaml 的交互邏輯 3 /// </summary> 4 public partial class CarItem : UserControl 5 { 6 private MCar car; 7 public CarItem() 8 { 9 InitializeComponent(); 10 } 11 12 public MCar Car 13 { 14 get { return this.car; } 15 set 16 { 17 this.car = value; 18 if (this.car == null) 19 { 20 return; 21 } 22 23 this.tbName.Text = this.car.Name; 24 string uriStr = string.Format(@"/images/car/{0}.png", this.car.Name); 25 this.imgLogo.Source = new BitmapImage(new Uri(uriStr, UriKind.Relative)); 26 } 27 } 28 }
然后是左邊的詳細模板,同樣采用UserControl的方式:
<UserControl x:Class="WPFUserControl.CarsView.MainForm.CarDetail" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <StackPanel> <Image x:Name="imgCarDetail"></Image> </StackPanel> </UserControl>
1 /// <summary> 2 /// CarDetail.xaml 的交互邏輯 3 /// </summary> 4 public partial class CarDetail : UserControl 5 { 6 private MCar car; 7 public CarDetail() 8 { 9 InitializeComponent(); 10 } 11 12 public MCar Car 13 { 14 get { return this.car; } 15 set 16 { 17 this.car = value; 18 if (this.car == null) 19 { 20 return; 21 } 22 23 string uriStr = string.Format(@"/images/car/{0}.png", this.car.Name); 24 this.imgCarDetail.Source = new BitmapImage(new Uri(uriStr, UriKind.Relative)); 25 } 26 } 27 }
最后是主窗體的XAML布局代碼:
<Window x:Class="WPFUserControl.CarsView.MainForm.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WPFUserControl.CarsView.MainForm" Title="WPF_USECONTROL_MAINWINDOWS" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="3*"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <local:CarDetail x:Name="carDetail" Grid.Row="0" Grid.Column="0"></local:CarDetail> <ListBox x:Name="lbCarBox" Grid.Row="0" Grid.Column="1"></ListBox> </Grid> </Window>
讓我們來看看后臺的數據綁定是怎樣實現的:
1 /// <summary> 2 /// MainWindow.xaml 的交互邏輯 3 /// </summary> 4 public partial class MainWindow : Window 5 { 6 public MainWindow() 7 { 8 InitializeComponent(); 9 this.InitializeCarView(); 10 } 11 12 public void InitializeCarView() 13 { 14 BCarManagement carManager = new BCarManagement(); 15 List<MCar> carList = carManager.GetCarItemList(); 16 List<CarItem> carItemList = (from c in carList select new CarItem() { Car = c }).ToList(); 17 this.lbCarBox.ItemsSource = carItemList; 18 this.lbCarBox.SelectionChanged += new SelectionChangedEventHandler((o, e) => 19 { 20 CarItem item = e.AddedItems[0] as CarItem; 21 if (item == null) 22 { 23 return; 24 } 25 26 this.carDetail.Car = item.Car; 27 }); 28 } 29 }
初一看,和之前的webform或是winform中的用戶控件相差無幾,基本未用到WPF的一些特性,
所以WPF中較為重要的Template就是接下來所要提及的。
DataTemplate
Template實際上包括DataTemplate和ControlTemplate,ControlTemplate我們將會在下一章節詳細介紹,
這里我們先用DataTemplate來改造之前的UserControl。將UserControl改造為DataTemplate十分簡單,
只需要做部分調整即可,顯示效果和前者是一模一樣,但是,結構上較之前者來講,清晰很多,這正是我們想要的。
我們將DataTemplate統一放到資源字典中,以便在不同的window中作為外部引用:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WPFDataTemplate.CarsView.MainForm"> <local:ImgUriConverter x:Key="imgConverter"></local:ImgUriConverter> <DataTemplate x:Key="carItemTemplate"> <StackPanel> <Grid> <Grid.RowDefinitions> <RowDefinition ></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*"></ColumnDefinition> <ColumnDefinition Width="1*"></ColumnDefinition> </Grid.ColumnDefinitions> <Image MaxWidth="100" Grid.Row="0" Grid.Column="0" Source="{Binding Name,Converter={StaticResource imgConverter}}" ></Image> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}"></TextBlock> </Grid> </StackPanel> </DataTemplate> <DataTemplate x:Key="carDetailTemplate"> <StackPanel> <Image Source="{Binding Name,Converter={StaticResource imgConverter}}"></Image> </StackPanel> </DataTemplate> </ResourceDictionary>
以下是MainWindow的XAML代碼:
<Window x:Class="WPFDataTemplate.CarsView.MainForm.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF_DATATEMPLATE_MAINWINDOWS" Height="350" Icon="images/car/bc0.png" Width="525" WindowStyle="None" BorderThickness="0" > <Window.Resources> <ResourceDictionary Source="CarViewTemplate.xaml"></ResourceDictionary> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="9*"></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="3*"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <UserControl Grid.Column="0" Content="{Binding SelectedItem,ElementName=lbCarBox}" ContentTemplate="{StaticResource carDetailTemplate}"></UserControl> <ListBox x:Name="lbCarBox" Grid.Row="0" Grid.Column="1" ItemTemplate="{StaticResource carItemTemplate}"></ListBox> <Button x:Name="btnOpen" Width="125" Height="20" Grid.Row="1" Grid.Column="1"></Button> </Grid> </Window>
其中要注意的地方是,需要提前將外部資源引用進來。
接下來事ManWindow的分離代碼(CodeBehind):
18 /// <summary> 19 /// MainWindow.xaml 的交互邏輯 20 /// </summary> 21 public partial class MainWindow : Window 22 { 23 public MainWindow() 24 { 25 InitializeComponent(); 26 this.lbCarBox.ItemsSource = new BCarManagement().GetCarItemList(); 27 this.KeyDown += new KeyEventHandler(MainWindow_KeyDown); 28 this.MouseDown += new MouseButtonEventHandler(MainWindow_MouseDown); 29 } 30 31 void MainWindow_MouseDown(object sender, MouseButtonEventArgs e) 32 { 33 if (e.LeftButton == MouseButtonState.Pressed) 34 { 35 this.DragMove(); 36 } 37 } 38 39 void MainWindow_KeyDown(object sender, KeyEventArgs e) 40 { 41 if (e.Key == Key.Escape) 42 { 43 this.Close(); 44 } 45 } 46 }
你只需要關心窗體加載時的初始化函數即可。
至此,DataTemplate的改造結束。
小結
UserControl準確的講是完全將view與data分離,分別對應在UserControl的XAML代碼和CodeBehind代碼,是一種顯式
的將數據從UI中移植到后臺,實際上他們最終會被編譯成同一個類;
DataTemplate則是利用WPF的數據綁定特性,利用Bind將數據隱式的與XAML結合,是一種對數據如何展示的模板封裝,
其便易性較好。
其實,UserControl與DataTemplate并無直接關系,UserControl直接體現在Control上,DataTemplate直接體現在
Template上。從上例中可看出我們將DataTemplate賦值給了UserControl的ContentTemplate屬性。所以UserControl
是宏觀上的控件,DataTemplate是對數據的模板化封裝。

浙公網安備 33010602011771號