[WP8] Binding時,依照DataType選擇DataTemplate
范例下載
范例程序代碼:點此下載
問題情景
在開發WPF、WP8...這類應用程序的時候,透過Binding機制搭配DataTemplate,能讓數據類別在經過Binding之后於畫面上呈現。例如下列的范例,透過Binding機制搭配DataTemplate,就能在WP8的ListBox控件中,依照DataTemplate的定義,來呈現Car對象集合。
-
執行結果
-
程序代碼(.CS)
namespace BindingSample001.Models { public class Car { public string Name { get; set; } } } namespace BindingSample001 { public partial class MainPage : PhoneApplicationPage { // Constructors public MainPage() { // Initialize this.InitializeComponent(); // Data var carList = new List<Car>(); carList.Add(new Car() { Name = "C001" }); carList.Add(new Car() { Name = "C002" }); carList.Add(new Car() { Name = "C003" }); carList.Add(new Car() { Name = "C004" }); carList.Add(new Car() { Name = "C005" }); // Binding this.ListBox001.ItemsSource = carList; } } }
-
程序代碼(.XAML)
<!--Resources--> <phone:PhoneApplicationPage.Resources> <!--Car Template--> <DataTemplate x:Key="CarTemplate"> <StackPanel Background="LightGreen" Margin="12,6" FlowDirection="LeftToRight"> <TextBlock Text="Car" /> <CheckBox Content="{Binding Path=Name}" /> </StackPanel> </DataTemplate> </phone:PhoneApplicationPage.Resources> <!--LayoutRoot--> <ListBox x:Name="ListBox001"> <!--ItemTemplate--> <ListBox.ItemTemplate> <StaticResource ResourceKey="CarTemplate" /> </ListBox.ItemTemplate> <!--Style--> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListBox.ItemContainerStyle> </ListBox>
而在一些更復雜的開發項目中,畫面上不單單只需要呈現一種的數據類別,而是需要呈現數據類別的各種延伸數據類別,並且這些不同種類的延伸數據類別,在經過Binding之后於畫面上必須要有不同種類的呈現外觀。例如下列的范例,Car類別為基礎數據類別,Truck類別、Sedan類別各自為Car類別的延伸類別,這些Truck類別、Sedan類別在經過Binding之后,於畫面上Truck類別會有一種呈現外觀、而Sedan類別會有另外一種呈現外觀。
本篇文章介紹如何在Binding時,依照數據類別的DataType選擇對應的DataTemplate,用以在Binding之后,於畫面上依照不同數據類別來呈現不同外觀,為自己留個紀錄也希望能幫助到有需要的開發人員。
-
類別結構
-
類別程序代碼(.CS)
public class Car { public string Name { get; set; } } public class Truck : Car { public int MaxLoad { get; set; } } public class Sedan : Car { public int MaxSpeed { get; set; } }
在WPF中,依照DataType選擇DataTemplate
在WPF中,可以定義DataTemplate.DataType這個屬性,用來指定DataTemplate所對應的數據類別。當定義這個屬性之后,WPF應用程序在Binding時,就可以依照數據對象的DataType選擇DataTemplate,並且使用這個DataTemplate的定義來呈現數據對象。例如下列的范例,Truck類別、Sedan類別在經過Binding之后,於畫面上Truck類別會有一種呈現外觀、而Sedan類別會有另外一種呈現外觀。
-
執行結果
-
程序代碼(.CS)
namespace BindingSample002 { public partial class MainWindow : Window { // Constructors public MainWindow() { // Initialize this.InitializeComponent(); // Source var carList = new List<Car>(); carList.Add(new Truck() { Name = "T001", MaxLoad = 100 }); carList.Add(new Sedan() { Name = "S002", MaxSpeed = 200 }); carList.Add(new Truck() { Name = "T003", MaxLoad = 300 }); carList.Add(new Truck() { Name = "T004", MaxLoad = 400 }); carList.Add(new Sedan() { Name = "S005", MaxSpeed = 500 }); // Binding this.ListBox001.ItemsSource = carList; } } }
-
程序代碼(.XAML)
<!--Resources--> <Window.Resources> <!--Truck Template--> <DataTemplate DataType="{x:Type models:Truck}"> <StackPanel Background="LightBlue" Margin="12,6" FlowDirection="LeftToRight"> <TextBlock Text="Truck" /> <CheckBox Content="{Binding Path=Name}" /> <CheckBox Content="{Binding Path=MaxLoad}" /> </StackPanel> </DataTemplate> <!--Sedan Template--> <DataTemplate DataType="{x:Type models:Sedan}"> <StackPanel Background="LightPink" Margin="12,6" FlowDirection="RightToLeft"> <TextBlock Text="Sedan" /> <TextBlock Text="{Binding Path=Name}" /> <TextBlock Text="{Binding Path=MaxSpeed}" /> </StackPanel> </DataTemplate> </Window.Resources> <!--LayoutRoot--> <ListBox x:Name="ListBox001"> <!--Style--> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListBox.ItemContainerStyle> </ListBox>
在WP8中,依照DataType選擇DataTemplate
因為一些原因,在WP8中目前沒有提供DataTemplate.DataType這個屬性,用來指定DataTemplate所對應的數據類別,這也讓WP8應用程序,沒有辦法提供在Binding之后,於畫面上依照不同數據類別來呈現不同外觀的這個功能。但是山不轉路轉,開發人員還是可以透過Binding機制、結合自定義的IValueConverter,來提供依照DataType選擇DataTemplate這個功能。
-
程序代碼(.XAML)
首先在頁面中使用下列的XAML宣告來取代原有的DataTemplate,讓DataTemplate的選擇機制,改為使用Binding以及自定義的TypeTemplateConverter來提供。
-
程序代碼(.XAML)
<!--LayoutRoot--> <ListBox x:Name="ListBox001"> <!--ItemTemplate--> <ListBox.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding}" ContentTemplate="{Binding Converter={StaticResource TypeTemplateConverter}}" HorizontalContentAlignment="Stretch" /> </DataTemplate> </ListBox.ItemTemplate> <!--Style--> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListBox.ItemContainerStyle> </ListBox>
接着就是依照下列的程序代碼來建立自定義的TypeTemplateConverter。這個自定義的TypeTemplateConverter實作IValueConverter,用來處理Binding的目標數據對象,並且依照目標數據對象的型別來提供DataTemplate。
這個設計直覺上會認為沒有問題,但實際撰寫這個Converter的時候會發現,接收目標數據對象、取得目標數據對象型別這些功能實作都沒有問題,但是如何取得DataTemplate卻是一個問題(范例中[???]部分的程序代碼)。在TypeTemplateConverter並沒有定義DataTemplate的來源,沒有來源就沒有辦法取得DataTemplate,那當然也就沒有辦法依照目標數據對象型別來提供DataTemplate。
-
程序代碼(.CS)
public sealed class TypeTemplateConverter : IValueConverter { // Methods public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // Require if (value == null) return null; // TypeName string typeName = value.GetType().ToString(); // DataTemplate DataTemplate dataTemplate = [???]; if (dataTemplate == null) return null; // Convert return dataTemplate; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
這時為了提供DataTemplate的來源,開發人員可以為TypeTemplateConverter類別加入System.Windows.FrameworkElement類別的繼承,這樣就可以使用FrameworkElement的Resources屬性做為DataTemplate的來源。在這之后TypeTemplateConverter就可以使用目標數據對象型別作為索引,取得Resources屬性之中的DataTemplate,用以提供Binding機制使用。
-
程序代碼(.CS)
namespace BindingSample003.Converters { public sealed class TypeTemplateConverter : System.Windows.FrameworkElement, IValueConverter { // Methods public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // Require if (value == null) return null; // TypeName string typeName = value.GetType().ToString(); // DataTemplate var dataTemplate = this.Resources[typeName] as DataTemplate; if (dataTemplate == null) return null; // Convert return dataTemplate; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
為TypeTemplateConverter類別加入System.Windows.FrameworkElement類別的繼承之后,在XAML定義中就可以使用XAML語法來定義TypeTemplateConverter對象所要提供的DataTemplate。
-
程序代碼(.XAML)
<!--Resources--> <phone:PhoneApplicationPage.Resources> <!--Converter--> <converters:TypeTemplateConverter x:Key="TypeTemplateConverter" > <converters:TypeTemplateConverter.Resources> <!--Truck Template--> <DataTemplate x:Key="BindingSample003.Models.Truck"> <StackPanel Background="LightBlue" Margin="12,6" FlowDirection="LeftToRight"> <TextBlock Text="Truck" /> <CheckBox Content="{Binding Path=Name}" /> <CheckBox Content="{Binding Path=MaxLoad}" /> </StackPanel> </DataTemplate> <!--Sedan Template--> <DataTemplate x:Key="BindingSample003.Models.Sedan"> <StackPanel Background="LightPink" Margin="12,6" FlowDirection="RightToLeft"> <TextBlock Text="Sedan" /> <TextBlock Text="{Binding Path=Name}" /> <TextBlock Text="{Binding Path=MaxSpeed}" /> </StackPanel> </DataTemplate> </converters:TypeTemplateConverter.Resources> </converters:TypeTemplateConverter> </phone:PhoneApplicationPage.Resources>
完成上列的設計與定義之后,透過Binding機制、結合自定義的TypeTemplateConverter,WP8應用程序在Binding時,就可以依照數據對象的DataType選擇DataTemplate,並且使用這個DataTemplate的定義來呈現數據對象。例如下列的范例,Truck類別、Sedan類別在經過Binding之后,於畫面上Truck類別會有一種呈現外觀、而Sedan類別會有另外一種呈現外觀。
-
執行結果
-
程序代碼(.CS)
namespace BindingSample003 { public partial class MainPage : PhoneApplicationPage { // Constructors public MainPage() { // Initialize this.InitializeComponent(); // Data var carList = new List<Car>(); carList.Add(new Truck() { Name = "T001", MaxLoad = 100 }); carList.Add(new Sedan() { Name = "S002", MaxSpeed = 200 }); carList.Add(new Truck() { Name = "T003", MaxLoad = 300 }); carList.Add(new Truck() { Name = "T004", MaxLoad = 400 }); carList.Add(new Sedan() { Name = "S005", MaxSpeed = 500 }); // Binding this.ListBox001.ItemsSource = carList; } } }
-
程序代碼(.XAML)
<!--Resources--> <phone:PhoneApplicationPage.Resources> <!--Converter--> <converters:TypeTemplateConverter x:Key="TypeTemplateConverter" > <converters:TypeTemplateConverter.Resources> <!--Truck Template--> <DataTemplate x:Key="BindingSample003.Models.Truck"> <StackPanel Background="LightBlue" Margin="12,6" FlowDirection="LeftToRight"> <TextBlock Text="Truck" /> <CheckBox Content="{Binding Path=Name}" /> <CheckBox Content="{Binding Path=MaxLoad}" /> </StackPanel> </DataTemplate> <!--Sedan Template--> <DataTemplate x:Key="BindingSample003.Models.Sedan"> <StackPanel Background="LightPink" Margin="12,6" FlowDirection="RightToLeft"> <TextBlock Text="Sedan" /> <TextBlock Text="{Binding Path=Name}" /> <TextBlock Text="{Binding Path=MaxSpeed}" /> </StackPanel> </DataTemplate> </converters:TypeTemplateConverter.Resources> </converters:TypeTemplateConverter> </phone:PhoneApplicationPage.Resources> <!--LayoutRoot--> <ListBox x:Name="ListBox001"> <!--ItemTemplate--> <ListBox.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding}" ContentTemplate="{Binding Converter={StaticResource TypeTemplateConverter}}" HorizontalContentAlignment="Stretch" /> </DataTemplate> </ListBox.ItemTemplate> <!--Style--> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListBox.ItemContainerStyle> </ListBox>