问题缘起

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

在WPF中使用变通方法实现枚举类型的XAML绑定的更多相关文章

  1. WPF中TreeView.BringIntoView方法的替代方案

    原文:WPF中TreeView.BringIntoView方法的替代方案 WPF中TreeView.BringIntoView方法的替代方案 周银辉 WPF中TreeView.BringIntoVie ...

  2. Windows Presentation Foundation(WPF)中的数据绑定(使用XmlDataProvider作控件绑定)

    原文:Windows Presentation Foundation(WPF)中的数据绑定(使用XmlDataProvider作控件绑定) ------------------------------ ...

  3. 在WPF中,如何得到任何Object对象的XAML代码?

    原文:在WPF中,如何得到任何Object对象的XAML代码? 在WPF中,可以使用System.Windows.Markup.XamlWriter.Save(objName)得到任何Object对象 ...

  4. WPF中MVVM模式下控件自有的事件绑定

    1.原因 在WPF中单纯的命令绑定往往不能满足覆盖所有的事件,例如ComboBox的SelectionChanged事件,DataGrid的SelectionChanged事件等等,这时就可以用事件绑 ...

  5. Asp.Net 之 枚举类型的下拉列表绑定

    有这样一个学科枚举类型: /// 学科 /// </summary> public enum Subject { None = , [Description("语文") ...

  6. WPF中三种方法得到当前屏幕的宽和高

    WPF程序中的单位是与设备无关的单位,每个单位是1/96英寸,如果电脑的DPI设置为96(每个英寸96个像素),那么此时每个WPF单位对应一个像素,不过如果电脑的DPI设备为120(每个英寸120个像 ...

  7. 接口中带参方法,传入IB类型的数据

    不同的接口有不同的方法 不同的类有不同的作用 不同的作用产生不一样的效果 不同的效果让程序看似复杂,实际简单... 比如此程序,看似复杂,实际就那么点事: 谁生成了谁,谁设置了谁,谁传入了谁,谁被谁调 ...

  8. wpf中ListBox的选中项与ComboBox间的绑定

    产品类: public class Product:NotificationObject { private int productID; public int ProductID { get { r ...

  9. .NET中,在方法参数的类型前加一个OUT是做什么用的

    话说古时候,在一个名字叫C#的繁华的大城市里面,有两家珠宝加工店,一家叫ref,另外一家叫out. 有一天,有名字叫a和b的两个人每人都各带了一公斤黄金要加工首饰. a去了ref店,ref的掌柜告诉a ...

随机推荐

  1. framebuffer line_length 參數

    以下是在 trace 關機充電動畫所遇到一個疑惑, 關於 framebuffer device 的屬性 line_length 大小值, 一個 display panel,1920 × 1080, 每 ...

  2. Java处理 文件复制

    try { InputStream in = new FileInputStream(new File(oldPath)); OutputStream out = new FileOutputStre ...

  3. Mysql主从架构的复制

    复制类型 (1)基于语句的复制:  在主服务器上执行的SQL语句,在从服务器上执行同样的语句.MySQL默认采用基于语句的复制,效率比较高.  一旦发现没法精确复制时,会自动选着基于行的复制. (2) ...

  4. Linux问题处理: stdio.h: No such file or directory linux

    本来打算编译一下<自制编程语言>一书的代码,结果提示错误: 其实说的还是很清楚的,一般出现这种情况都是没有安装相应的库,所以: 再次编译,成功: 测试代码: # comment print ...

  5. 【JavaScript】ArtTemplate个人的使用体验。

    据说ArtTemplate是腾讯的,感觉这东西真不错,使用方便,用起来很简单,哈哈.腾讯也不完全只是坑爹啊. ArtTemplate 使用是,正常引入js,这个自然不用说.这东西啥时候使用呢?我觉得这 ...

  6. 在Python中使用可变长参数列表

    函数定义 在函数定义中使用*args和**kwargs传递可变长参数. *args用作传递非命名键值可变长参数列表(位置参数); **kwargs用作传递键值可变长参数列表 函数调用 在调用函数时,使 ...

  7. hibernate+mysql的连接池配置

    1:连接池的必知概念    首先,我们还是老套的讲讲连接池的基本概念,概念理解清楚了,我们也知道后面是怎么回事了. 以前我们程序连接数据库的时候,每一次连接数据库都要一个连接,用完后再释放.如果频繁的 ...

  8. 深入理解Java中的String

    一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class String implements java.io.Ser ...

  9. Bubble Cup 8 finals H. Bots (575H)

    题意: 简单来说就是生成一棵树,要求根到每个叶子节点上的路径颜色排列不同, 且每条根到叶子的路径恰有n条蓝边和n条红边. 求生成的树的节点个数. 1<=n<=10^6 题解: 简单计数. ...

  10. Linux C--信号 sigaction函数

    使用 sigaction 函数: signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受 到了一定的限制.而 POSIX 标准定义的信号处 ...