关于枚举的双语显示问题。

在WinForm开发中,经常会遇到开发双语版本的问题,利用Resource功能,很容易实现。而前不久,我就遇到了一个难题——枚举的双语显示问题,问题如下:

这里先定义一个枚举:
enum Sex
{
    Male,
    Female
}

然后,我们在WinForm一个窗口中放入一个ComboBox(命名为cbxSex),并把它的数据源绑定到Sex枚举,代码如下:
cbxSex.DataSource = enum.GetValues(typeof(Sex));

此时,界面显示如下:


但是,此时要做中文版时,发现没法在不修改cbxSex.DataSource = enum.GetValues(typeof(Sex))的基础上显示中文的“男”、“女”。(我要的是能
通过cbxSex.SelectedValue来直接获取这个枚举值。)

一个很自然的想法,是实现自定义格式化,即通过IFormatable、IFormatProvider、ICustomFormatter等实现。经查MSDN,发现Enum基类实现了IFormatable,但我这个Sex枚举好像没法Override这个IFormatable接口(IFormatable的两个方法在Enum中都标上了“Obsolete”,估计微软准备在Enum中去掉IFormatable接口实现吧)。如果通过IFormatProvider、ICustomFormatter来实现,不仅繁琐不说,而且,ComboBox也没有一个FormatProvider属性供设置。因此,问题陷入了一个僵局。(不知道大伙能不能通过这三个接口给出一个比较完美的解决方案。)

最后,我想到一个自觉还不错的办法。

既然我要的是能通过cbxSex.SelectedValue来直接获取这个枚举值,我何不对Sex枚举进行下包装,然后利用ComboBox的DisplayMember和ValueMember属性来实现双语显示呢。想到这里,我写了以下这个类:

    public class EnumValueStringPair
    {
        private readonly Enum m_Enum;

        public EnumValueStringPair(Enum _enum)
        {
            this.m_Enum = _enum;
        }

        /// <summary>
        /// 获取实际的枚举值。
        /// </summary>
        public Enum Enum
        {
            get { return this.m_Enum; }
        }

        /// <summary>
        /// 获取该枚举值对应的字符串。该字段从对应的资源文件中提取文本。
        /// </summary>
        public string EnumString
        {
            get { return Properties.Resources.ResourceManager.GetString(this.m_Enum.ToString()); }
        }
    }

在默认资源与中文资源中分别添加两项,如图:



然后,我在窗口中加入以下两个静态自读字段
        private static readonly EnumValueStringPair m_Male = new EnumValueStringPair(Sex.Male);
        private static readonly EnumValueStringPair m_Female = new EnumValueStringPair(Sex.Female);

最后,把cbxSex绑定到这两个字段组成的列表中:
            List<EnumValueStringPair> list = new List<EnumValueStringPair>();
            list.Add(m_Male);
            list.Add(m_Female);
            this.cbxSex.DataSource = list;
            this.cbxSex.DisplayMember = "EnumString";
            this.cbxSex.ValueMember = "Enum";

中文效果如下:


至此,双语版的Enum显示问题就解决了,而且,如果以后要添加别的语种的Enum显示,只需添加对应语种的.resx文件即可,另外,还可以方便的使用cbxSex.SelectedValue来直接获取Sex枚举值,也可以直接将cbxSex.SelectedValue设置为Sex.Male或Sex.Female.

后记:
       好久好久没写过文章了,发现写得还真有点垃圾,算了,权作为平生第一篇像样的Blog吧。大家是否有更好的解决办法,欢迎讨论^_^

posted @ 2008-03-24 17:45 中华小鹰 阅读(2030) 评论(20)  编辑 收藏 网摘

  回复  引用  查看    
#1楼2008-03-24 18:17 | 江大鱼      
是不是可以在ItemAdd之类的事件触发之后再转化一下呢?这样就没必要改变帮定方法了·
  回复  引用    
#2楼2008-03-24 18:22 | 卡卡北[未注册用户]
jhh0111同学,我把这个文章转到
http://www.leadnt.org/a/a.asp?B=11&ID=57&AUpflag=1&ANum=1
了啊

  回复  引用  查看    
#3楼2008-03-24 20:42 | Dflying Chen      
这段代码:
private static readonly EnumValueStringPair m_Male = new EnumValueStringPair(Sex.Male);
private static readonly EnumValueStringPair m_Female = new EnumValueStringPair(Sex.Female);

如果只有两个还好说,如果几十个,岂不是会很郁闷?

  回复  引用  查看    
#4楼2008-03-24 22:15 | 簡簡單單..      
^ō^ 想法不错, 就是有点麻烦...
  回复  引用  查看    
#5楼[楼主]2008-03-24 23:47 | 中华鹰      
--引用--------------------------------------------------
Dflying Chen: 这段代码:
private static readonly EnumValueStringPair m_Male = new EnumValueStringPair(Sex.Male);
private static readonly EnumValueStringPair m_Female = new EnumValueStringPair(Sex.Female);

如果只有两个还好说,如果几十个,岂不是会很郁闷?
--------------------------------------------------------
是啊,这是一个缺点,但还好只是体力劳动。
另外,我也找过别的方案,没有找到更好的,不知道大家能不能给出一个更好的方案?

  回复  引用  查看    
#6楼2008-03-25 09:45 | 代码乱了      
我还是比较喜欢用Attribute
  回复  引用  查看    
#7楼[楼主]2008-03-25 10:04 | 中华鹰      
--引用--------------------------------------------------
代码乱了: 我还是比较喜欢用Attribute
--------------------------------------------------------
你好!能不能向你请教一下用Attribute怎么实现?非常感谢^_^

  回复  引用  查看    
#8楼2008-03-25 10:12 | Azuresong      
List<EnumValueStringPair> list = new List<EnumValueStringPair>();
list.Add(m_Male);
list.Add(m_Female);


这段代码换成enum.GetValues(typeof(Sex));来做就不用再声明
m_Male 和 m_Female了;

  回复  引用  查看    
#9楼2008-03-25 13:11 | 腊八粥      
comboBox的Item是一个Object,你可以把它用一个类传进去,并覆盖这个类的ToString()方法,使它的输出为你指定的字符串资源,就可以了。不要绑定DataSource和DataMember。挺简单的东西,不必要搞得太复杂。
  回复  引用  查看    
#10楼[楼主]2008-03-25 15:56 | 中华鹰      
--引用--------------------------------------------------
腊八粥: comboBox的Item是一个Object,你可以把它用一个类传进去,并覆盖这个类的ToString()方法,使它的输出为你指定的字符串资源,就可以了。不要绑定DataSource和DataMember。挺简单的东西,不必要搞得太复杂。
--------------------------------------------------------
enum的ToString()好像没法Override

  回复  引用  查看    
#11楼2008-03-25 16:15 | 小智      

-- 以前在网上copy的一段代码(出处记不得了,不好意思),拿出来分享

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum)]
 public class EnumDescription : Attribute
 {
  private string enumDisplayText;
  private int enumRank;
  private FieldInfo fieldIno;

  /// <summary>
  /// 描述枚举值
  /// </summary>
  /// <param name="enumDisplayText">描述内容</param>
  /// <param name="enumRank">排列顺序</param>
  public EnumDescription( string enumDisplayText, int enumRank )
  {
   this.enumDisplayText = enumDisplayText;
   this.enumRank = enumRank;
  }

  /// <summary>
  /// 描述枚举值,默认排序为5
  /// </summary>
  /// <param name="enumDisplayText">描述内容</param>
  public EnumDescription( string enumDisplayText )
   : this(enumDisplayText, 5) { }

  public string EnumDisplayText
  {
   get { return this.enumDisplayText; }
  }

  public int EnumRank
  {
   get { return enumRank; }
  }

  public int EnumValue
  {
   get { return (int)fieldIno.GetValue(null); }
  }

  public string FieldName
  {
   get { return fieldIno.Name; }
  }

  #region  =========================================对枚举描述属性的解释相关函数

  /// <summary>
  /// 排序类型
  /// </summary>
  public enum SortType
  {
   /// <summary>
   ///按枚举顺序默认排序
   /// </summary>
   Default,
   /// <summary>
   /// 按描述值排序
   /// </summary>
   DisplayText,
   /// <summary>
   /// 按排序熵
   /// </summary>
   Rank
  }

  private static System.Collections.Hashtable cachedEnum = new Hashtable();


  /// <summary>
  /// 得到对枚举的描述文本
  /// </summary>
  /// <param name="enumType">枚举类型</param>
  /// <returns></returns>
  public static string GetEnumText( Type enumType )
  {
   EnumDescription[] eds = (EnumDescription[])enumType.GetCustomAttributes(typeof(EnumDescription), false);
   if ( eds.Length != 1 ) return string.Empty;
   return eds[0].EnumDisplayText;
  }

  /// <summary>
  /// 获得指定枚举类型中,指定值的描述文本。
  /// </summary>
  /// <param name="enumValue">枚举值,不要作任何类型转换</param>
  /// <returns>描述字符串</returns>
  public static string GetFieldText( object enumValue )
  {
   EnumDescription[] descriptions = GetFieldTexts(enumValue.GetType(), SortType.Default);
   foreach ( EnumDescription ed in descriptions )
   {
    if ( ed.fieldIno.Name == enumValue.ToString() ) return ed.EnumDisplayText;
   }
   return string.Empty;
  }


  /// <summary>
  /// 得到枚举类型定义的所有文本,按定义的顺序返回
  /// </summary>
  /// <exception cref="NotSupportedException"></exception>
  /// <param name="enumType">枚举类型</param>
  /// <returns>所有定义的文本</returns>
  public static EnumDescription[] GetFieldTexts( Type enumType )
  {
   return GetFieldTexts(enumType, SortType.Default);
  }

  /// <summary>
  /// 得到枚举类型定义的所有文本
  /// </summary>
  /// <exception cref="NotSupportedException"></exception>
  /// <param name="enumType">枚举类型</param>
  /// <param name="sortType">指定排序类型</param>
  /// <returns>所有定义的文本</returns>
  public static EnumDescription[] GetFieldTexts( Type enumType, SortType sortType )
  {
   EnumDescription[] descriptions = null;
   //缓存中没有找到,通过反射获得字段的描述信息
   if ( cachedEnum.Contains(enumType.FullName) == false )
   {
    FieldInfo[] fields = enumType.GetFields();
    ArrayList edAL = new ArrayList();
    foreach ( FieldInfo fi in fields )
    {
     object[] eds = fi.GetCustomAttributes(typeof(EnumDescription), false);
     if ( eds.Length != 1 ) continue;
     ((EnumDescription)eds[0]).fieldIno = fi;
     edAL.Add(eds[0]);
    }

    cachedEnum.Add(enumType.FullName, (EnumDescription[])edAL.ToArray(typeof(EnumDescription)));
   }
   descriptions = (EnumDescription[])cachedEnum[enumType.FullName];
   if ( descriptions.Length <= 0 ) throw new NotSupportedException("枚举类型[" + enumType.Name + "]未定义属性EnumValueDescription");

   //按指定的属性冒泡排序
   for ( int m = 0; m < descriptions.Length; m++ )
   {
    //默认就不排序了
    if ( sortType == SortType.Default ) break;

    for ( int n = m; n < descriptions.Length; n++ )
    {
     EnumDescription temp;
     bool swap = false;

     switch ( sortType )
     {
      case SortType.Default:
       break;
      case SortType.DisplayText:
       if ( string.Compare(descriptions[m].EnumDisplayText, descriptions[n].EnumDisplayText) > 0 ) swap = true;
       break;
      case SortType.Rank:
       if ( descriptions[m].EnumRank > descriptions[n].EnumRank ) swap = true;
       break;
     }

     if ( swap )
     {
      temp = descriptions[m];
      descriptions[m] = descriptions[n];
      descriptions[n] = temp;
     }
    }
   }

   return descriptions;
  }

  #endregion
 }


  回复  引用  查看    
#12楼2008-03-25 16:52 | 代码乱了      
@中华鹰
你的方案也有可取的地方的,Attribute的实现和楼上贴的差不多

  回复  引用  查看    
#13楼2008-03-25 21:13 | 腊八粥      
中华鹰: --引用--------------------------------------------------
腊八粥: comboBox的Item是一个Object,你可以把它用一个类传进去,并覆盖这个类的ToString()方法,使它的输出为你指定的字符串资源,就可以了。不要绑定DataSource和DataMember。挺简单的东西,不必要搞得太复杂。
--------------------------------------------------------
enum的ToString()好像没法Override
--------------------------------------------------------

楼主理解错了。我举个例子:
/// <summary>
/// 用于指示连接字符串的数据库环境类型
/// </summary>
public enum ServerEnvironmentType
{
/// <summary>
/// 指示数据库处于开发环境。
/// </summary>
Develop = 0,
/// <summary>
/// 指示数据库处于集成测试(SIT)环境。
/// </summary>
SystemIntegrationTesting = 1,
}

//要放置到ComboBox中的项
internal class PropertyItem
{
string name;
ServerEnvironmentType type;

public PropertyItem(string itemName, ServerEnvironmentType itemType)
{
name = itemName;
type = itemType;
}

public override string ToString()
{
return name;
}

public ServerEnvironmentType Type
{
get { return type; }
}
}

设置comboBox的Item的代码类似下面这样写:
comboBox_Property.Items.Clear();
comboBox_Property.Items.Add(new PropertyItem(Develop的双语字符串, ServerEnvironmentType.Develop));
comboBox_Property.Items.Add(new PropertyItem(SIT的双语字符串, ServerEnvironmentType.SystemIntegrationTesting));

要获取枚举值,就使用类似
ServerEnvironmentType value = ((PropertyItem)comboBox_Property.SelectValue).Type;

  回复  引用  查看    
#14楼[楼主]2008-03-26 13:28 | 中华小鹰      
to 腊八粥

我明白你的方案了,其实你的方案并不比我的简单,而且,我个人觉得你的方案并不能实现多语显示(除非做两个版本)

你也使用了一个PropertyItem类作为绑定源,这跟我使用EnumValueStringPair几乎一模一样,不一样的是,你是直接硬编码返回显示值,而我是从资源中读取而已。因此,我的方案可以在不更改源代码的情况下添加新的语种(只要添加新的语种的资源),你的恐怕就不行了。

ComboBox在没有设置“DisplayMember“这个属性时,显示值是直接调用对象的ToString()方法,在没有设置“ValueMember”时,属性SelectValue的值也正是对象本身,你正是用了这两点。我觉得设置“DisplayMember" 与"ValueMember“是一种更好的方案。

  回复  引用  查看    
#17楼[楼主]2008-03-27 17:57 | 中华小鹰      
嗯。看了,也理解了。
我又想到了一个问题,有时候一个ComboBox中供选择的枚举值并不是某个枚举的所有枚举值,而只是一部份,这样的话就不能直接
this.comboBox1.DataSource = new EnumDataSource2<Sex>();
而需要手动构造你的EnumAdapt类。

我觉得可以为EnumDataSource添加一个构造方法
public EnumDataSource2(params EnumType[] @enums)
{
if (enums == null || enums.Length <= 0)
throw new ArgumentException();
foreach (EnumType value in enums)
{
base.Add(new EnumAdapter(value));
}
}

以满足这个需求




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1120046




相关文章:

相关链接: