WPF:DataTemplateSelector设置控件不同的样式

时间:2023-03-08 16:16:35

原文 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,而且实现的方式看上去比较优雅。