最近想實作這麼個東西,一個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">
<ComboBox ItemsSource="{Binding CombinedValue}"></ComboBox>
<DataTemplate x:Key="dateTime">
<DatePicker Text="{Binding CombinedValue}" ></DatePicker>
</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>{"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;
/// 資料的類型
public string TypeName
如果運作成功,我們可以看到一個下拉框,一個文本框,一個日期選擇控件都做為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;
}
這樣,我們就無需引入後端的實體(Model)對象,保證了前端的幹淨。
運作起來,還是沒有問題,仔細看DataTypeTemplateSelector對象的SelectTemplate
方法,還是有點醜陋,這裡把CombinedEntity的TypeName屬性寫死,萬一TypeName改成ControlName或其他名字,控
件則無法按照預期顯示。
再次重構,首先修改綁定對象CombinedEntity
public class CombinedEntity
/// 顯示控件的類型
public Type ControlType
修改ListBox綁定資料源
entities = new List<CombinedEntity>()
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)}
最後再次修改DataTypeTemplateSelector對象的SelectTemplate 方法
public override DataTemplate SelectTemplate(object item, DependencyObject container)
Type controlType = null;
if (pi.PropertyType == typeof(Type))
controlType = (Type)pi.GetValue(item, null);
if (controlType == typeof(TextBox))
if (controlType == typeof(ComboBox))
if (controlType == typeof(DatePicker))
這樣,要顯示不同的控件,在ControlType裡面定義即可,然後在XAML添加DataTemplate,在DataTemplateSelector對象中根據不同的ControlType傳回不同的DataTemplate,而且實作的方式看上去比較優雅。