當資料項目需根據屬性值或全域狀態動態套用不同 DataTemplate 時,DataTemplateSelector 是最佳解決方案。本文透過兩個完整範例 (XML 資料綁定與硬體偵測狀態) 示範如何實作自訂 Template Selector,並搭配資源字典管理多樣化模板。
DataTemplateSelector 為抽象類別,僅需覆寫 SelectTemplate(object item, DependencyObject container) 方法,依邏輯回傳對應 DataTemplate。容器參數可用於 FindResource 尋找具名模板。
public class LocalNameTemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
XmlElement data = item as XmlElement;
if (data != null) {
return ((FrameworkElement)container).FindResource(data.LocalName) as DataTemplate;
}
return null;
}
}
<DataTemplate x:Key="Book" DataType="{x:Type sx:XmlElement}">
<StackPanel Orientation="Horizontal">
<Rectangle Margin="2" Width="14" Height="14" Fill="Brown"/>
<TextBlock VerticalAlignment="Center" Text="{Binding XPath=@Title}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CD" DataType="{x:Type sx:XmlElement}">
<StackPanel Orientation="Horizontal">
<Ellipse Margin="2" Width="14" Height="14" Fill="Silver"/>
<TextBlock VerticalAlignment="Center" Text="{Binding XPath=@Title}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DVD" DataType="{x:Type sx:XmlElement}">
<StackPanel Orientation="Horizontal">
<Ellipse Margin="2" Width="14" Height="14" Fill="Blue"/>
<TextBlock VerticalAlignment="Center" Text="{Binding XPath=@Title}"/>
</StackPanel>
</DataTemplate>
<Window x:Class="WpfProgressBarDemo.DataTemplateSelectorDemo"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sx="clr-namespace:System.Xml;assembly=System.Xml"
Title="DataTemplateSelector" Height="300" Width="300"
DataContext="{DynamicResource dataSource}">
<Window.Resources>
<XmlDataProvider x:Key="dataSource">
<x:XData>
<Media xmlns="">
<Book Author="Aretch" Title="WCF全面解析"/>
<Book Author="ByVoid" Title="Node.js開發指南"/>
<Book Author="Rogers Cardenhead" Title="21天學通Java"/>
<CD Title="沒有CD了"/>
<DVD Title="《十面埋伏》"/>
</Media>
</x:XData>
</XmlDataProvider>
<!-- 上述三個 DataTemplate 放這裡 -->
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding XPath=/Media/*}">
<ListBox.ItemTemplateSelector>
<l:LocalNameTemplateSelector xmlns:l="clr-namespace:WpfProgressBarDemo"/>
</ListBox.ItemTemplateSelector>
</ListBox>
</Grid>
</Window>
public class LocalNameTemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if (item != null && item is DeviceCheckInfo) {
DeviceCheckInfo device = item as DeviceCheckInfo;
Window2 win = new Window2(); // 注意:實務上應避免 new Window,改用 Application.Current.FindResource 或傳入容器
if (device.CheckResult) return win.FindResource("dui") as DataTemplate;
else return win.FindResource("cuo") as DataTemplate;
}
return null;
}
}
public class DeviceCheckInfo {
public string Name { get; set; }
public string CheckContent { get; set; }
public bool CheckResult { get; set; }
}
<Window.Resources>
<DataTemplate x:Key="dui" DataType="{x:Type local:DeviceCheckInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="10" VerticalAlignment="Center" FontSize="20"/>
<TextBlock Text="{Binding Path=CheckContent}" Margin="10" VerticalAlignment="Center" FontSize="20"/>
<Path x:Name="dui" Data="M43,5 L20,40 20,40 0,20 6,15 18,26 37,7 43,5 z"
Fill="Green" Margin="5" Stretch="Fill"/>
<CheckBox Name="checkbox" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="cuo" DataType="{x:Type local:DeviceCheckInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="10" VerticalAlignment="Center" FontSize="20"/>
<TextBlock Text="{Binding Path=CheckContent}" Margin="10" VerticalAlignment="Center" FontSize="20"/>
<Path Margin="5" Data="M50,25 L25,50 M25,25 50,50"
Fill="#FFF4F4F5" Height="40" Stretch="Fill" Stroke="Red" Width="40" StrokeThickness="8"/>
<CheckBox Name="checkbox" />
</StackPanel>
</DataTemplate>
<local:LocalNameTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
<ListBox Name="lbtest" ItemTemplateSelector="{StaticResource myDataTemplateSelector}" />
若切換邏輯單純 (如依單一屬性值),也可在 DataTemplate 內使用 DataTrigger 切換 ContentTemplate 或直接修改視覺樹屬性,無需撰寫 Selector 類別。但 Selector 更適合複雜邏輯、跨專案重用、單元測試。
DataTemplateSelector 讓 WPF 列表控件擁有「根據資料動態換裝」的能力。配合資源字典集中管理模板、FindResource 動態載入,能構建極具彈性的 UI 呈現層。本文兩個範例分別展示 XML 元素名稱與布林屬性兩種常見選擇依據,涵蓋從資料綁定到視覺化的完整鏈路。