原文 WPF:DataTemplateSelector设置控件不同的样式
最近想实现这么个东西,一个ListBox, 里面的ListBoxItem可能是文本框、下拉框、日期选择控件等等。
很自然的想到了DataTemplateSelector,并且事先定义好各类DataTemplate以显示不同的控件。
先定义好各类资源
<Window.Resources> <DataTemplate x:Key="textBox"> <Border BorderBrush="Gray" BorderThickness="1"> <TextBox Text="{Binding CombinedValue}"></TextBox> </Border> </DataTemplate> <DataTemplate x:Key="comboBox"> <Border BorderBrush="Gray" BorderThickness="1"> <ComboBox ItemsSource="{Binding CombinedValue}"></ComboBox> </Border> </DataTemplate> <DataTemplate x:Key="dateTime"> <Border BorderBrush="Gray" BorderThickness="1"> <DatePicker Text="{Binding CombinedValue}" ></DatePicker> </Border> </DataTemplate> </Window.Resources>
然后在ListBox中设置ItemDataTemplateSelector
<ListBox ItemsSource="{Binding}"> <ListBox.ItemTemplateSelector> <local:DataTypeTemplateSelector TextBoxTemplate="{StaticResource textBox}" ComboBoxTemplate="{StaticResource comboBox}" DateTimeTemplate="{StaticResource dateTime}"></local:DataTypeTemplateSelector> </ListBox.ItemTemplateSelector> </ListBox>
新建一个类继承DataTemplateSelector
public class DataTypeTemplateSelector:DataTemplateSelector { public DataTemplate TextBoxTemplate { get; set; } public DataTemplate ComboBoxTemplate { get; set; } public DataTemplate DateTimeTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { CombinedEntity entity = item as CombinedEntity; //CombinedEnity为绑定数据对象 string typeName = entity.TypeName; if (typeName == "TextBox") { return TextBoxTemplate; } if (typeName == "ComboBox") { return ComboBoxTemplate; } if (typeName == "DateTime") { return DateTimeTemplate; } return null; } }
设置好DataContext,即可运行
public partial class CombinedControl : Window { public List<CombinedEntity> entities; public CombinedControl() { InitializeComponent(); entities = new List<CombinedEntity>() { new CombinedEntity{ CombinedValue=new List<string>{"","",""}, TypeName="ComboBox"}, new CombinedEntity{ CombinedValue ="Test", TypeName="TextBox"}, new CombinedEntity{ CombinedValue=DateTime.Now, TypeName="DateTime"} }; this.DataContext = entities; } } public class CombinedEntity { /// <summary> /// 绑定数据的值 /// </summary> public object CombinedValue { get; set; } /// <summary> /// 数据的类型 /// </summary> public string TypeName { get; set; } }
如果运行成功,我们可以看到一个下拉框,一个文本框,一个日期选择控件都做为ListBox的子项显示在窗口中。
但是,我发现,在DataTypeTemplateSelector对象的SelectTemplate 方法中,居然需要把item对象转换成我们的绑定数据对象
CombinedEntity entity = item as CombinedEntity; //CombinedEnity为绑定数据对象
这意味着前台需要引入后端的业务逻辑,代码的味道相当不好,不过没有关系,我们有强大的反射工具,重构下代码:
public override DataTemplate SelectTemplate(object item, DependencyObject container) { Type t = item.GetType(); string typeName = null; PropertyInfo[] properties = t.GetProperties(); foreach (PropertyInfo pi in properties) { if (pi.Name == "TypeName") { typeName = pi.GetValue(item, null).ToString(); break; } } if (typeName == "TextBox") { return TextBoxTemplate; } if (typeName == "ComboBox") { return ComboBoxTemplate; } if (typeName == "DateTime") { return DateTimeTemplate; } return null; }
这样,我们就无需引入后端的实体(Model)对象,保证了前端的干净。
运行起来,还是没有问题,仔细看DataTypeTemplateSelector对象的SelectTemplate
方法,还是有点丑陋,这里把CombinedEntity的TypeName属性硬编码,万一TypeName改成ControlName或其他名字,控
件则无法按照预期显示。
再次重构,首先修改绑定对象CombinedEntity
public class CombinedEntity { /// <summary> /// 绑定数据的值 /// </summary> public object CombinedValue { get; set; } /// <summary> /// 显示控件的类型 /// </summary> public Type ControlType { get; set; } }
修改ListBox绑定数据源
entities = new List<CombinedEntity>() { new CombinedEntity{ CombinedValue=new List<string>{"","",""}, ControlType = typeof(ComboBox)}, new CombinedEntity{ CombinedValue ="Test", ControlType = typeof(TextBox)}, new CombinedEntity{ CombinedValue=DateTime.Now, ControlType = typeof(DatePicker)} }; this.DataContext = entities;
最后再次修改DataTypeTemplateSelector对象的SelectTemplate 方法
public override DataTemplate SelectTemplate(object item, DependencyObject container) { Type t = item.GetType(); Type controlType = null; PropertyInfo[] properties = t.GetProperties(); foreach (PropertyInfo pi in properties) { if (pi.PropertyType == typeof(Type)) { controlType = (Type)pi.GetValue(item, null); break; } } if (controlType == typeof(TextBox)) { return TextBoxTemplate; } if (controlType == typeof(ComboBox)) { return ComboBoxTemplate; } if (controlType == typeof(DatePicker)) { return DateTimeTemplate; } return null; }
这样,要显示不同的控件,在ControlType里面定义即可,然后在XAML添加DataTemplate,在DataTemplateSelector对象中根据不同的ControlType返回不同的DataTemplate,而且实现的方式看上去比较优雅。