最近想實現這么個東西,一個ListBox, 里面的ListBoxItem可能是文本框、下拉框、日期選擇控件等等。
很自然的想到了DataTemplateSelector,並且事先定義好各類DataTemplate以顯示不同的控件。
先定義好各類資源

< 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.ItemTemplateSelector >
< local:DataTypeTemplateSelector TextBoxTemplate =" {StaticResource textBox} "
ComboBoxTemplate =" {StaticResource comboBox} "
DateTimeTemplate =" {StaticResource dateTime} " ></ local:DataTypeTemplateSelector >
</ ListBox.ItemTemplateSelector >
</ ListBox >
新建一個類繼承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 List<CombinedEntity> entities;
public CombinedControl()
{
InitializeComponent();
entities = new List<CombinedEntity>()
{
new CombinedEntity{ CombinedValue= new List< string>{ " 1 ", " 2 ", " 3 "}, 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對象轉換成我們的綁定數據對象
這意味着前台需要引入后端的業務邏輯,代碼的味道相當不好,不過沒有關系,我們有強大的反射工具,重構下代碼:

{
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

{
/// <summary>
/// 綁定數據的值
/// </summary>
public object CombinedValue
{
get;
set;
}
/// <summary>
/// 顯示控件的類型
/// </summary>
public Type ControlType
{
get;
set;
}
}
修改ListBox綁定數據源

{
new CombinedEntity{ CombinedValue= new List< string>{ " 1 ", " 2 ", " 3 "}, ControlType = typeof(ComboBox)},
new CombinedEntity{ CombinedValue = " Test ", ControlType = typeof(TextBox)},
new CombinedEntity{ CombinedValue=DateTime.Now, ControlType = typeof(DatePicker)}
};
this.DataContext = entities;
最后再次修改DataTypeTemplateSelector對象的SelectTemplate 方法

{
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,而且實現的方式看上去比較優雅。