zoukankan      html  css  js  c++  java
  • 在WPF中使用变通方法实现枚举类型的XAML绑定

    问题缘起

    WPF的分层结构为编程带来了极大便利,XAML绑定是其最主要的特征。在使用绑定的过程中,大家都普遍的发现枚举成员的绑定是个问题。一般来说,枚举绑定多出现于与ComboBox配合的情况,此时我们希望实现的目标有:

    1. 建立选择项与ItemsSource的对应关系;
    2. 自动获取用于ItemsSource的枚举源;
    3. 自定义下拉框中显示的内容。

    对于目标1,考虑最简单的模式,即枚举的定义采用从0开始的连续整数,可以使用IValueConverter接口来实现从枚举到整型的双向转换,以使得枚举成员绑定到SelectedIndex上。

    有些朋友提出使用静态ObjectDataProvider资源作为ItemsSource的来源,这种方式可实现枚举成员的直接绑定,不需要值转换,其缺点是对于每一个枚举类型都要添加一个提供者,当项目较大、枚举类型多时使用起来很不方便。

    考虑到枚举的比较是值类型比较,Broculos想到了比较聪明的方法同时实现了目标1和目标2:定义一个返回枚举兄弟成员的标记拓展(MarkupExtension)。使用时向标记拓展中提供枚举类型即可。相比于上一种方法,该方法更加简单明了,只是当在XAML中提供枚举类型时,需要引用其命名空间,而XAML中的命名空间引用缺少自动完成机制,有时需要搜索一番。

    当然,上面所有解答都没有实现目标3。网友ding.li使用代码方式对下拉框内容进行设置,虽然实现了目标3,但违背了目标2。

    Mgen通过定义一个提供附加属性的类实现了所有3个目标。在Selector要素中,设置EnumSelector.EnumType和EnumSelector.BindingPath附加属性来指定ItemsSource和SelectedItem,而非直接设置要素的相应属性。该方法实际上发展了标记拓展的思路,为实现目标3而进行了较复杂的设计。这是非常好的实现方案,缺点是违反直观感受。

    变通方案

    本文介绍使用封装枚举类型的方法同时实现3个愿望。主要思路是,在XAML中尽可能少的写入代码,通过上下文绑定直接设置下拉框的ItemsSource,SelectedItem,以及显示内容。

    注意到DisplayMemberPath和Binding拓展的Path属性,要同时由上下文提供多个属性用来绑定,分别是:作为ItemsSource源的集合、作为SelectedItem的实例、及作为“DisplayMember”的字符串。显然直接使用枚举实例无法提供所需属性成员,因此设计封装类型并在其中定义成员如下:

    public class EnumShell<T>
    {
        public T Instance { get; }
        public string Description {get;}
        public EnumShell[] Brothers {get;}
    }
    

    这样在XAML中的下拉框代码绑定可写为:

    <ComboBox ItemsSource="{Binding Path=Brothers}" SelectedItem="{Binding}" DisplayMemberPath="Description"/>

    可以通过EnumShell实例上下文获取所有必要信息。

    下面简述该类型的实现。

    泛型类EnumShell<T>的构造函数接受类型T的参数,该参数是特定枚举类型的实例,因此T即为某个枚举类型。该参数由Instance属性保存,并从枚举类型获取到所有可用的枚举值,均封装为EnumShell<T>实例,并作为数组由Brothers属性公开。Description属性获取枚举值定义的DescriptionAttribute特定指定的文本,作为显示的内容。

    考虑到ComboBox的项比较实际是EnumShell<T>实例的比较,因此不能单纯的使用其构造函数来得到Brothers集合,解决方法是定义一个静态的字典用于保存EnumShell<T>实例,使得通过一个枚举值永远得到唯一的一个EnumShell<T>实例。

    完整的代码如下所示:

        public abstract class EnumShell
        {
            static Dictionary<string, EnumShell> pool = new Dictionary<string, EnumShell>();
    
            public static EnumShell<T> GetShell<T>(T Instance)
            {
                var key = typeof(T).ToString() + Instance.ToString();
                if (pool.ContainsKey(key))
                {
                    return (EnumShell<T>)pool[key];
                }
                else
                {
                    var nsh = new EnumShell<T>(Instance);
                    pool.Add(key, nsh);
                    return nsh;
                }
            }
        }
    
        public class EnumShell<T> : EnumShell
        {
            internal EnumShell(T Instance)
            {
                this.Instance = Instance;
            }
    
            public T Instance { get; set; }
    
            public Type EnumType { get { return typeof(T); } }
    
            public string Description
            {
                get
                {
                    string strValue = Name;
                    FieldInfo fieldinfo = Instance.GetType().GetField(strValue);
                    Object[] objs = fieldinfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
                    if (objs == null || objs.Length == 0)
                    {
                        return strValue;
                    }
                    else
                    {
                        DescriptionAttribute da = (DescriptionAttribute)objs[0];
                        return da.Description;
                    }
                }
            }
    
            public string Name { get { return Instance.ToString(); } }
            public string FullName { get { return EnumType.ToString() + Name; } }
    
            public T[] BrotherInstances { get { return (T[])Enum.GetValues(this.EnumType); } }
    
            public EnumShell<T>[] Brothers { get {
                return BrotherInstances.Select(i => EnumShell.GetShell(i)).ToArray();
            } }
        }
    

    定义抽象的EnumShell类型作为基类型,可以在定义实体类型时不指明泛型类型参数,由此支持任意枚举类型的取值;定义静态的GetShell泛型函数,将新的EnumShell实例注册添加到全局字典;将EnumShell<T>构造函数的可见性进行限制,避免了自行实例化导致字典中没有注册的情况;EnumShell类公开了其他属性成员,以方便各种XAML绑定的情况。

    使用时,将实体类型中的原枚举属性替换为EnumShell或EnumShell<T>属性,并使用EnumShell.GetShell进行实例化赋值。如,有枚举定义:

    public enum Cup
    {
        [Description("very nice")]
        A,
        [Description("nice")]
        B,
        [Description("another kind of nice")]
        C
    }
    

    定义某实体类型,用EnumShell表示该枚举:

    public class SecretWeapon
    {
        public SecretWeapon(){
            this.Cup = EnumShell.GetShell<Cup>(Cup.A);
        }
    
        public EnumShell<Cup> Cup { get; set; }
    }
    

    这样定义后即可使用,当需要执行switch分支时,可用Instance属性获取被封装的真正的枚举值。

    结语

    本文介绍的封装方法实现枚举绑定,适用于自定义实体类型的情况,对于直接使用第三方实体类型的情况,则无法直接使用,必须对实体类型本身进行再次封装,而这大大降低了便利性。

    其实在早些时候有传言说C# 5.0会支持拓展属性,试想这要是真的,对枚举进行绑定将是轻而易举的事情,真的希望C#能够实现这一功能。

    本文中的EnumShell类型在Heroius.Extension.WPF库中包含。更多可免费使用的程序集引用,请使用Heroius的Nuget服务源:http://heroius.com:8686/app/nuget。详细信息请访问http://heroius.com:8686/app

  • 相关阅读:
    一段滚动文字的js (jQuery)
    VB ASP 使用 now() 时默认格式调整方法
    解决标题过长的CSS
    javascript Spline代码
    统计学中的几个重要的分布
    网页游戏开发秘笈 PDF扫描版
    网页设计与开发——HTML、CSS、JavaScript (王津涛) pdf扫描版
    网页设计与开发:HTML、CSS、JavaScript实例教程 (郑娅峰) pdf扫描版
    网页DIV+CSS布局和动画美化全程实例 (陈益材) 随书光盘
    实用掌中宝--HTML&CSS常用标签速查手册 PDF扫描版
  • 原文地址:https://www.cnblogs.com/heroius/p/4187852.html
Copyright © 2011-2022 走看看