关于枚举类型的多语言显示,其实就是Globalization的问题。解决方案当然不止一种,这里介绍一种可用性和扩展性的比较好的通用方法。
显然这里自己去实现自定义格式化,即通过IFormatable、IFormatProvider、ICustomFormatter等接口已达到Globalization有点小题大作了,而另外一个很容易想到的点是通过DiaplayMember实现显示值得自定义(对于简单Binding,例如ComboBox、ListBox等只用重载ToString就可以了)。
首先,我们希望Binding整个枚举类型的每一个值,也就是说,我们需要把这个枚举的所有值变成一个数据源,为了实现这一点,我们可以使用Enum上的helper方法Enum.GetValues(Type)来返回一个对所有值得枚举,然后依次添加到IList对象或者IListSource接口即可。
if (!typeof(EnumType).IsEnum) { throw new NotSupportedException("Can not support type: " + typeof(EnumType).FullName); // It's better use resource version as below. // throw new NotSupportedException(SR.GetString("TYPE_NOT_SUPPORT", typeof(EnumType).FullName)); } // Use Enum helper enumerator list all enum values and add to current context. foreach (EnumType value in Enum.GetValues(typeof(EnumType))) { //TODO: add each value to IList base.Add(new EnumAdapter(value)); }
然后,取到了值,由于我们希望自定义Binding显示,那么需要对枚举值进行封装,而在这个封装里面,我们可以实现多语言的支持。
////// Enum value adapter, used to get values from each Cultures. /// public sealed class EnumAdapter { /**/////// Storage the actual Enum value. /// private EnumType _value; /**/////// Constructor an /// The enum value. ///. /// /// /// public EnumAdapter(EnumType value) { if (!Enum.IsDefined(typeof(EnumType), value)) { throw new ArgumentException(string.Format("{0} is not defined in {1}", value, typeof(EnumType).Name), "value"); // It's better use resource version as below. // throw new ArgumentException(SR.GetString("ENUM_NOT_DEFINED_FMT_KEY", value, typeof(EnumType).Name), "value"); } _value = value; } /**/////// Gets the actual enum value. /// public EnumType Value { get { return _value; } } /**/////// Gets the display value for enum value by search local resource with currrent UI lture /// and special key which is concated from Enum type name and Enum value name. /// ////// This would get correct display value by accessing location resource with current UI Culture. /// public string DisplayValue { get { return SR.GetString(string.Format("{0}.{1}", typeof(EnumType).Name, _value.ToString())); } } //TODO: If you want more, please add below }
至此,整个功能的框架已经完成,下面我们来看看一些细节——如何对资源读取和管理的封装:
////// Constructor a new internal SR() { //TODO: If you modified resource location, please update here this.resources = new System.Resources.ResourceManager( string.Concat(typeof(EnumAdapter).Namespace, ".Resource"), base.GetType().Assembly); } /**////. /// /// Get singleton instance. /// ///A singleton private static SR GetLoader() { if (loader == null) { lock (SR.InternalSyncObject) { if (loader == null) { loader = new SR(); } } } return loader; } /**/////// Gets an object from resources by special key, which provided by /// Resource accessed key ///. /// return stored object in resource. if resource not found, return public static object GetObject(string name) { SR loader = GetLoader(); if (loader == null) { return null; } try { return loader.resources.GetObject(name, Culture); } catch { } return name; } /**////as object. /// Gets a string from resources by special key, which provided by /// Resource accessed key ///. /// return stored string in resource. If resource not found, retuen public static string GetString(string name) { SR loader = GetLoader(); if (loader == null) { return null; } try { return loader.resources.GetString(name, Culture); } catch { } return name; } /**////as result. /// Gets a formatted string from resources by special key, which provided by /// Resource accessed key /// format arguments. ///and optional parameters. /// return stored string in resource. If resource not found, use as formator, return the formatted string. 0x400)) { args[i] = arg.Substring(0, 0x3fd) + ""; } } return string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args); }
OK,大功告成,有了这么一个封装,在应用里就可以简单的这么几句够搞定。
private void Form1_Load(object sender, EventArgs e) { this.comboBox1.DataSource = new EnumDataSource(); this.comboBox1.DisplayMember = "DisplayValue"; this.comboBox1.ValueMember = "Value"; } public enum Sex { Male, Female }