将TypeConverter直接绑定到枚举类型

6
我可以提供帮助进行翻译。以下是需要翻译的内容:

我有一个场景:我有一个枚举,并希望将其绑定并显示在 DataGridView(数据绑定)中的 DataGridViewTextBoxColumn 上。

这是我的枚举:

//[TypeConverter(typeof(EnumStringConverter))]
   public enum YesNoNA
   {
      [EnumDescription("Yes")]
      Yes,
      [EnumDescription("No")]
      No,
      [EnumDescription("N/A")]
      NA
   }

这里是一个使用它的简单属性:
  [TypeConverter(typeof(EnumStringConverter))]
  public YesNoNA HighLimitWithinBounds { get; protected set; }

在我上面的情况下,类型转换器工作得很好。它为我进行了转换。
然而,这比我的理想解决方案更复杂。如果我将类型转换器放在枚举本身上(取消上面的代码的注释),并注释掉属性上的类型转换器,则不再调用类型转换器!
我已经在其他类上使用了这种约定,而且它也很好地工作。
为什么直接在枚举上放置类型转换器不起作用?
供参考,这是我的类型转换器:
  public class EnumStringConverter : TypeConverter
   {
      public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, Object value, Type destinationType)
      {
         if (value != null && destinationType == typeof(string))
         {
            return "Edited to protect the innocent!";
         }
         return TypeDescriptor.GetConverter(typeof(Enum)).ConvertTo(context, culture, value, destinationType);
      }
      public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
      {
         if (destinationType == typeof(string))
         {
            return true;
         }
         return base.CanConvertTo(context, destinationType);
      }
   };

可能是如何在C#枚举中覆盖ToString?的重复问题。 - Jim Mischel
绝对不相关。那个帖子中答案的解决方案已经在上面实现了。这个问题将更深入地探讨。 - greggorob64
1个回答

3
我不确定你所说的“这比我的理想解决方案更复杂”是什么意思。我有一种不同于你的方法,但它可能不会更简单。我认为我的方法需要更多的前期工作,但随着在应用程序中的使用次数越来越多,它会得到更好的回报。它可以使您的应用程序成为本地化,并且意味着您不必为每个枚举值添加属性。
1) 制作资源管理器缓存
这部分是可选的;然而,如果您像我一样经常使用多个资源文件,这可以通过减少反射量来提高性能。
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Resources;

namespace AppResourceLib.Public.Reflection
{
  internal static class ResourceManagerCache
  {
    private static Dictionary<Type, ResourceManager> _resourceManagerMap =
      new Dictionary<Type, ResourceManager>();

    public static ResourceManager GetResourceManager(Type resourceType)
    {
      ResourceManager resourceManager = null;

      // Make sure the type is valid.
      if (null != resourceType)
      {
        // Try getting the cached resource manager.
        if (!ResourceManagerCache._resourceManagerMap.TryGetValue(resourceType, out resourceManager))
        {
          // If it is not in the cache create it.
          resourceManager = resourceType.InvokeMember(
            "ResourceManager",
            (BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic),
            null,                                                   
            null,                                                   
            null) as ResourceManager;

          // If it was created, add the resource manager to the cache.
          if (null != resourceManager)
          {
            ResourceManagerCache._resourceManagerMap.Add(resourceType, resourceManager);
          }
        }
      }

      return resourceManager;
    }
  }
}

2) 创建本地化说明属性

这是您将应用于枚举类型的属性。很酷的事情是,您不必为每个枚举添加属性,只需在枚举类型声明上方添加一次即可。

using System;
using System.ComponentModel;

namespace AppResourceLib.Public.Reflection
{
  /// <summary>
  /// A resource type attribute that can be applied to enumerations.
  /// </summary>
  [AttributeUsage(AttributeTargets.Enum)]
  public sealed class LocalizedDescriptionAttribute : Attribute
  {
    /// <summary>
    /// The type of resource associated with the enum type.
    /// </summary>
    private Type _resourceType;

    public LocalizedDescriptionAttribute(Type resourceType)
    {
      this._resourceType = resourceType;
    }

    /// <summary>
    /// The type of resource associated with the enum type.
    /// </summary>
    public Type ResourceType
    {
      get
      {
        return this._resourceType;
      }
    }
  }
}

3) 创建本地化描述转换器

这将把枚举值转换为您在字符串资源(.resx)文件中提供的字符串。

using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Windows.Data;

namespace AppResourceLib.Public.Reflection
{
  [ValueConversion(typeof(Object), typeof(String))]
  public class LocalizedDescriptionConverter : IValueConverter
  {
    public Object Convert(Object value, Type targetType, Object param, CultureInfo cultureInfo)
    {
      String description = null;

      if (null != value)
      {
        // If everything fails then at least return the value.ToString().
        description = value.ToString();

        // Get the LocalizedDescriptionAttribute of the object.
        LocalizedDescriptionAttribute attribute =
          value.GetType().GetCustomAttribute(typeof(LocalizedDescriptionAttribute))
          as LocalizedDescriptionAttribute;

        // Make sure we found a LocalizedDescriptionAttribute.
        if (null != attribute)
        {          
          ResourceManager resourceManager = 
            ResourceManagerCache.GetResourceManager(attribute.ResourceType);

          if (null != resourceManager)
          {
            // Use the ResourceManager to get the description you gave the object value.
            // Here we just use the object value.ToString() (the name of the object) to get
            // the string in the .resx file. The only constraint here is that you have to
            // name your object description strings in the .resx file the same as your objects.
            // The benefit is that you only have to declare the LocalizedDescriptionAttribute
            // above the object type, not an attribute over every object.
            // And this way is localizable.
            description = resourceManager.GetString(value.ToString(), cultureInfo);

            String formatString = (param as String);

            // If a format string was passed in as a parameter,
            // make a string out of that.
            if (!String.IsNullOrEmpty(formatString))
            {
              formatString = formatString.Replace("\\t", "\t");
              formatString = formatString.Replace("\\n", "\n");
              formatString = formatString.Replace("\\r", "\r");

              description = String.Format(formatString, value.ToString(), description);              
            }          
          }
        }
      }

      return description;      
    }

    public Object ConvertBack(Object value, Type targetType, Object param, CultureInfo cultureInfo)
    {
      throw new NotImplementedException();

      return null;
        }
  }
}

4) 创建资源 (.resx) 字符串文件

现在,您需要创建一个包含所需枚举键值样式描述的资源文件。我的意思是,在字符串资源的“名称”列中,您将放置单个枚举的确切名称,并在“值”列中放置转换该枚举时要获取的字符串。
例如,假设您有以下枚举:

public enum MyColors
{
  Black,
  Blue,
  White
}

那么你的字符串资源文件应该长这样...

名称 | 值

黑色 | 暗色
蓝色 | 酷色
白色 | 亮色

5) 创建带有属性的枚举

现在我们最终创建带有LocalizedDescription的枚举声明。你传递给LocalizedDescription属性的参数是你的字符串资源文件类型。现在当转换器被使用时,它将看到枚举类型的属性,获取资源文件,查找与特定枚举值的字符串值匹配的键字符串,并返回从资源文件中转换后的字符串作为值。

using AppResourceLib.Public;
using AppResourceLib.Public.Reflection;

namespace MyEnums
{
  [LocalizedDescription(typeof(MyColorStrings))]
  public enum MyColors
  {
    Black,
    Blue,
    White
  }
}

这种方法的主要缺点是,只有当你资源文件中的“Name”键与你的枚举值名称匹配时才能起作用。这是在不给每个枚举一个描述属性的情况下引用资源文件中字符串值的唯一方法。那么如何将其用于显示值呢?以下是示例...
在您的xaml代码中,您需要创建一个数据提供程序来获取枚举的值到UI元素(我在这里使用ComboBox...)。然后,您需要使转换器可用并模板化您的UI元素以使用枚举转换器。所以,让我们开始吧...
      <!-- Enum Colors -->
  <ObjectDataProvider x:Key="MyColorEnums"
                      MethodName="GetValues"
                      ObjectType="{x:Type sys:Enum}">
    <ObjectDataProvider.MethodParameters>
      <x:Type TypeName="MyColors"/>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>


  <!-- Enum Type Converter -->
  <LocalizedDescriptionConverter x:Key="EnumConverter"/>


  <!-- Dropdown Expand ComboBox Template -->
  <DataTemplate x:Key="MyColorsComboBoxTemplate">
    <Label Content="{Binding Path=., Mode=OneWay,
      Converter={StaticResource EnumConverter}}"
           Height="Auto" Margin="0" VerticalAlignment="Center"/>
  </DataTemplate>

  <!-- And finally the ComboBox that will display all of your enum values
    but will use the strings from the resource file instead of enum.ToString() -->
  <ComboBox Width="80" HorizontalAlignment="Left"
  ItemTemplate="{StaticResource MyColorsComboBoxTemplate}"
  ItemsSource="{Binding Source={StaticResource MyColorEnums}}">

哇,对不起这个很长。我不确定这是否对您来说太复杂,但这是另一种选择。希望能帮到您!


1
我的示例与你的非常相似(你在使用资源文件方面更进一步,而我在其他地方也使用它来获得多语言支持)。我提到的复杂度差异是,我的示例需要在属性上使用类型转换器,而不是在枚举本身上使用。不幸的是,我认为我们的示例不会匹配。你正在使用xaml,而我正在使用纯.NET WinForms。你正在使用combobox。你的xaml专门将combobox绑定到所需的转换器,这正是我试图避免的(我不认为我会成功)。感谢回复! - greggorob64
哦,我明白你的意思了。很抱歉我无法帮助你。 - akagixxer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接