[WPF] DataTemplate Binding to Interface
在WPF内可以使用DataTemplate,来辅助完成对对象集合做DataBinding的工作。并且透过DataTemplate的DataType属性,来让对象集合中不同的对象,经过DataBinding之后能有「不同的外观」。简单的范例如下:不同的车辆类型,会依照车辆类型,呈现不同的详细数据。
namespace BindingInterfaceSample { public class Car { public string Name { get; set; } } public class Truck : Car { public int MaxLoad { get; set; } } public class Roadster : Car { public int MaxSpeed { get; set; } } }
<Window x:Class="BindingInterfaceSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:clk="clr-namespace:BindingInterfaceSample" Title="MainWindow" Height="350" Width="525"> <ItemsControl > <ItemsControl.Items> <clk:Truck Name="Truck01" MaxLoad="100" /> <clk:Truck Name="Truck02" MaxLoad="500" /> <clk:Roadster Name="Roadster01" MaxSpeed="99" /> </ItemsControl.Items> <ItemsControl.Resources> <DataTemplate DataType="{x:Type clk:Truck}"> <WrapPanel> <TextBlock Text="Truck" /> <TextBlock Text="{Binding Path=MaxLoad}" /> </WrapPanel> </DataTemplate> <DataTemplate DataType="{x:Type clk:Roadster}"> <WrapPanel> <TextBlock Text="Roadster" /> <TextBlock Text="{Binding Path=MaxSpeed}" /> </WrapPanel> </DataTemplate> </ItemsControl.Resources> </ItemsControl> </Window>
反过来说,也可以经由DataTemplate的DataType属性,来让对象集合中不同的对象,经过DataBinding之后能有「相同的外观」。简单的范例如下:只要是车,都只呈现车的数据。
namespace BindingInterfaceSample { public class Car { public string Name { get; set; } } public class Truck : Car { public int MaxLoad { get; set; } } public class Roadster : Car { public int MaxSpeed { get; set; } } }
<Window x:Class="BindingInterfaceSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:clk="clr-namespace:BindingInterfaceSample" Title="MainWindow" Height="350" Width="525"> <ItemsControl > <ItemsControl.Items> <clk:Truck Name="Truck01" MaxLoad="100" /> <clk:Truck Name="Truck02" MaxLoad="500" /> <clk:Roadster Name="Roadster01" MaxSpeed="99" /> </ItemsControl.Items> <ItemsControl.Resources> <DataTemplate DataType="{x:Type clk:Car}"> <Button Content="{Binding Path=Name}" /> </DataTemplate> </ItemsControl.Resources> </ItemsControl> </Window>
而在让对象集合中不同的对象,经过DataBinding之后能有「相同的外观」。这个使用情景中,DataTemplate的DataType如果设定为Interface型别,在WPF中会出现意外的执行结果。简单的范例如下:DataType设定为Interface型别,会无法对应DataTemplate。
namespace BindingInterfaceSample { public interface ICar { string Name { get; set; } } public class Truck : ICar { public string Name { get; set; } public int MaxLoad { get; set; } } public class Roadster : ICar { public string Name { get; set; } public int MaxSpeed { get; set; } } }
<Window x:Class="BindingInterfaceSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:clk="clr-namespace:BindingInterfaceSample" Title="MainWindow" Height="350" Width="525"> <ItemsControl > <ItemsControl.Items> <clk:Truck Name="Truck01" MaxLoad="100" /> <clk:Truck Name="Truck02" MaxLoad="500" /> <clk:Roadster Name="Roadster01" MaxSpeed="99" /> </ItemsControl.Items> <ItemsControl.Resources> <DataTemplate DataType="{x:Type clk:ICar}"> <Button Content="{Binding Path=Name}" /> </DataTemplate> </ItemsControl.Resources> </ItemsControl> </Window>
遇到必须设定DataTemplate的DataType为Interface型别的情景。解决的方法很简单:只要改写数据系结容器使用的DataTemplateSelector,增加以Interface型别做索引,查询Resource里的DataTemplate,再用这个DataTemplate来做DataBinding。简单的范例如下:只要是有实做车接口,都只呈现车的数据。
namespace BindingInterfaceSample { public interface ICar { string Name { get; set; } } public class Truck : ICar { public string Name { get; set; } public int MaxLoad { get; set; } } public class Roadster : ICar { public string Name { get; set; } public int MaxSpeed { get; set; } } }
<Window x:Class="BindingInterfaceSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:clk="clr-namespace:BindingInterfaceSample" Title="MainWindow" Height="350" Width="525"> <ItemsControl> <ItemsControl.Items> <clk:Truck Name="Truck01" MaxLoad="100" /> <clk:Truck Name="Truck02" MaxLoad="500" /> <clk:Roadster Name="Roadster01" MaxSpeed="99" /> </ItemsControl.Items> <ItemsControl.Resources> <DataTemplate DataType="{x:Type clk:ICar}"> <Button Content="{Binding Path=Name}" /> </DataTemplate> </ItemsControl.Resources> <ItemsControl.ItemTemplateSelector> <clk:StandardDataTemplateSelector /> </ItemsControl.ItemTemplateSelector> </ItemsControl> </Window>
namespace BindingInterfaceSample { public class StandardDataTemplateSelector : DataTemplateSelector { // Methods public override DataTemplate SelectTemplate(object item, DependencyObject container) { #region Require if (item == null) throw new ArgumentNullException(); if (container == null) throw new ArgumentNullException(); #endregion // Result DataTemplate itemDataTemplate = null; // Base DataTemplate itemDataTemplate = base.SelectTemplate(item, container); if (itemDataTemplate != null) return itemDataTemplate; // Interface DataTemplate FrameworkElement itemContainer = container as FrameworkElement; if (itemContainer == null) return null; foreach (Type itemInterface in item.GetType().GetInterfaces()) { itemDataTemplate = itemContainer.TryFindResource(new DataTemplateKey(itemInterface)) as DataTemplate; if (itemDataTemplate != null) break; } // Return return itemDataTemplate; } } }
参考数据
Style、DataTemplate 和隐含索引键
DataTemplateKey 类别
范列下载
BindingInterfaceSample点此下载
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。