2

我有一个Enum需要显示在ComboBox. 我已经设法使用组合框获取枚举值,ItemsSource并且我正在尝试对它们进行本地化。我认为这可以使用值转换器来完成,但由于我的枚举值已经是字符串,编译器会抛出IValueConverter无法将字符串作为输入的错误。我不知道有任何其他方法可以将它们转换为其他字符串值。还有其他方法可以做到这一点(不是本地化而是转换)?

我正在使用这个 marku 扩展来获取枚举值

[MarkupExtensionReturnType(typeof (IEnumerable))]
public class EnumValuesExtension : MarkupExtension {
    public EnumValuesExtension() {}

    public EnumValuesExtension(Type enumType) {
        this.EnumType = enumType;
    }

    [ConstructorArgument("enumType")]
    public Type EnumType { get; set; }
    public override object ProvideValue(IServiceProvider serviceProvider) {
        if (this.EnumType == null)
            throw new ArgumentException("The enum type is not set");
        return Enum.GetValues(this.EnumType);
    }
}

在 Window.xaml 中

<Converters:UserTypesToStringConverter x:Key="userTypeToStringConverter" />
....
<ComboBox ItemsSource="{Helpers:EnumValuesExtension Data:UserTypes}" 
            Margin="2" Grid.Row="0" Grid.Column="1" SelectedIndex="0" TabIndex="1" IsTabStop="False">
    <ComboBox.ItemTemplate>
        <DataTemplate DataType="{x:Type Data:UserTypes}">
            <Label Content="{Binding Converter=userTypeToStringConverter}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

这里是转换器类,它只是一个测试类,还没有本地化。

public class UserTypesToStringConverter : IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        return (int) ((Data.UserTypes) value) == 0 ? "Fizička osoba" : "Pravna osoba";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
         return default(Data.UserTypes);
     }
}

- 编辑 -

枚举由 ADO.NET Diagram 生成,不能更改。

4

4 回答 4

3

是的,当您将值传递给转换器时,它将成为stringEnum (EnumConverter) 的默认类型转换器,因为GetStandardValues(ie Enum.GetValues()) 将字段的可枚举作为字符串返回。

解决此问题的最佳方法是编写自定义类型转换器来装饰您的枚举。幸运的是,您不是第一个需要此功能的人,请参阅下面的代码示例。

public class EnumTypeConverter : EnumConverter
{
    public EnumTypeConverter()
        : base(typeof(Enum))
    {
    }

    public EnumTypeConverter(Type type)
        : base(type)
    {
    }

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || TypeDescriptor.GetConverter(typeof(Enum)).CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
            return GetEnumValue(EnumType, (string)value);

        if (value is Enum)
            return GetEnumDescription((Enum)value);

        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (value is Enum && destinationType == typeof(string))
            return GetEnumDescription((Enum)value);

        if (value is string && destinationType == typeof(string))
            return GetEnumDescription(EnumType, (string)value);

        return base.ConvertTo(context, culture, value, destinationType);
    }

    public static bool GetIsEnumBrowsable(Enum value)
    {
        var fieldInfo = value.GetType().GetField(value.ToString());
        var attributes = (BrowsableAttribute[])fieldInfo.GetCustomAttributes(typeof(BrowsableAttribute), false);

        return !(attributes.Length > 0) || attributes[0].Browsable;
    }

    public static string GetEnumDescription(Enum value)
    {
        var fieldInfo = value.GetType().GetField(value.ToString());
        var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

        return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
    }

    public static string GetEnumDescription(Type value, string name)
    {
        var fieldInfo = value.GetField(name);
        var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
        return (attributes.Length > 0) ? attributes[0].Description : name;
    }

    public static object GetEnumValue(Type value, string description)
    {
        var fields = value.GetFields();
        foreach (var fieldInfo in fields)
        {
            var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attributes.Length > 0 && attributes[0].Description == description)
                return fieldInfo.GetValue(fieldInfo.Name);

            if (fieldInfo.Name == description)
                return fieldInfo.GetValue(fieldInfo.Name);
        }

        return description;
    }

    public override bool GetPropertiesSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        return base.GetStandardValues(context);
    }

}

用法

[TypeConverter(typeof(EnumTypeConverter))]
public enum UserTypes : int
{
    [Browsable(false)]
    Unkown,    
    [Description("Local")]
    LocalUser,
    [Description("Network")]
    NetworkUser,
    [Description("Restricted")]
    RestrictedUser
} 

如您所见,上面的枚举我们使用Description属性来装饰每个字段,并使用用户朋友描述,并覆盖类型转换器以首先查找此属性。

不是 100%,但要让它与您的代码一起使用,您还需要将您的代码更改MarkupExtension为以下内容(注意:我尚未对此进行测试,因此需要您做一些工作)。

[MarkupExtensionReturnType(typeof (IEnumerable))]
public class EnumValuesExtension : MarkupExtension {

    public EnumValuesExtension() {}

    public EnumValuesExtension(Type enumType) 
    {
        this.EnumType = enumType;
    }

    [ConstructorArgument("enumType")]
    public Type EnumType { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider) 
    {
        if (this.EnumType == null)
            throw new ArgumentException("The enum type is not set");

        var converter = TypeDescriptor.GetConverter(this.EnumType);
        if (converter != null && converter.GetStandardValuesSupported(this.EnumType))        
            return converter.GetStandardValues(this.EnumType);

        return Enum.GetValues(this.EnumType);
    }
}

此外,我只对应用程序进行了有限的本地化,但我相信这是最好和最可维护的方法,因为它能够利用现有的 .NET 本地化工具(例如卫星程序集)

于 2012-09-18T15:18:45.517 回答
0

编辑:

您可以简单地在您的视图模型上添加一个返回MyEnumType[]并简单地返回的属性MyEnumType.GetValues()。然后在数据绑定点,您将有枚举值来引用而不是字符串值。

您指定的帮助程序类乍一看似乎很干净,但实际上并非如此 - 涉及很多杂乱无章的东西,并且您在视图中做出可能最好留给视图模型的决策。根据您对问题的看法,在视图模型中公开枚举值是有意义的。允许的值(即使是全部)可以被视为业务逻辑的一个方面,而不是视图的一个方面。

如果这还不足以解决问题,我的其余答案可能仍然对您有所帮助。


编辑前:

您可以通过简单地使用字符串资源来解决此问题,除了资源键通常在视图中进行硬编码。

所以我研究了是否有一种方法可以动态绑定字符串资源键,并找到了其他答案:

其中第二个似乎是一个干净而简单的选择。

在寻找的同时,我还发现了这篇博文:

这是他们示例中的代码。

看法:

<DataTemplate>
  <Button Command="{Binding}" Padding="2" Margin="2" Width="100" Height="100">
    <StackPanel>
      <Image HorizontalAlignment="Center"
             Width="60"
             app:ResourceKeyBindings.SourceResourceKeyBinding="{Binding Converter={StaticResource ResourceKeyConverter}, ConverterParameter=Image.{0}}"/>
      <TextBlock Text="{ext:ResourceKeyBinding Path=Name, StringFormat=Caption.{0} }" HorizontalAlignment="Center" FontWeight="Bold" Margin="0,2,0,0"/>
    </StackPanel>
  </Button>
</DataTemplate>

资源:

<Application.Resources>
  <BitmapImage x:Key="Image.AngryCommand" UriSource="Angry.png"/>
  <BitmapImage x:Key="Image.CoolCommand" UriSource="Cool.png"/>
  <BitmapImage x:Key="Image.HappyCommand" UriSource="Happy.png"/>

  <sys:String x:Key="Caption.Angry">Angry. Rrrr!</sys:String>
  <sys:String x:Key="Caption.Happy">Happy. Ha ha!</sys:String>
  <sys:String x:Key="Caption.Cool">Chilled out</sys:String>
</Application.Resources>

启用字符串资源键绑定的标记扩展:

public class ResourceKeyBindingExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var resourceKeyBinding = new Binding()
        {
            BindsDirectlyToSource = BindsDirectlyToSource,
            Mode = BindingMode.OneWay,
            Path = Path,
            XPath = XPath,
        };

        //Binding throws an InvalidOperationException if we try setting all three
        // of the following properties simultaneously: thus make sure we only set one
        if (ElementName != null)
        {
            resourceKeyBinding.ElementName = ElementName;
        }
        else if (RelativeSource != null)
        {
            resourceKeyBinding.RelativeSource = RelativeSource;
        }
        else if (Source != null)
        {
            resourceKeyBinding.Source = Source;
        }

        var targetElementBinding = new Binding();
        targetElementBinding.RelativeSource = new RelativeSource()
        {
            Mode = RelativeSourceMode.Self
        };

        var multiBinding = new MultiBinding();
        multiBinding.Bindings.Add(targetElementBinding);
        multiBinding.Bindings.Add(resourceKeyBinding);

        // If we set the Converter on resourceKeyBinding then, for some reason,
        // MultiBinding wants it to produce a value matching the Target Type of the MultiBinding
        // When it doesn't, it throws a wobbly and passes DependencyProperty.UnsetValue through
        // to our MultiBinding ValueConverter. To circumvent this, we do the value conversion ourselves.
        // See http://social.msdn.microsoft.com/forums/en-US/wpf/thread/af4a19b4-6617-4a25-9a61-ee47f4b67e3b
        multiBinding.Converter = new ResourceKeyToResourceConverter()
        {
            ResourceKeyConverter = Converter,
            ConverterParameter = ConverterParameter,
            StringFormat = StringFormat,
        };

        return multiBinding.ProvideValue(serviceProvider);
    }

    [DefaultValue("")]
    public PropertyPath Path { get; set; }

    // [snipped rather uninteresting declarations for all the other properties]
}
于 2012-09-18T15:19:54.223 回答
0

我使用通用资源转换器来执行此操作。您只需要指定要使用的资源管理器,并将前缀作为转换器参数传递:

class ResourceConverter : IValueConverter
{
    public ResourceManager ResourceManager { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (ResourceManager == null)
            throw new InvalidOperationException("The resource manager is not set");

        if (value == null)
            return string.Empty;
        string prefix = parameter as string ?? string.Empty;
        string resourceKey = prefix + value;
        if (string.IsNullOrEmpty(resourceKey))
            return string.Empty;

        return ResourceManager.GetString(resourceKey);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

假设你有一个这样的枚举:

class MyEnum
{
    Foo,
    Bar,
    Baz
}

以及名为 MyEnum_Foo、MyEnum_Bar 和 MyEnum_Baz 的资源,您可以像这样使用它:

<Window.Resources>
    <my:ResourceConverter x:Key="resourceConverter" ResourceManager="{x:Static prop:Resources.ResourceManager}" />
</Window.Resources>

...


<Label Content="{Binding Converter=resourceConverter, ConverterParameter=MyEnum_}" />
于 2012-09-18T15:28:47.807 回答
0

您可能对以下博客文章中描述的 LocalizedList 类感兴趣:http ://wpfglue.wordpress.com/2010/01/14/localized-value-formatting-in-wpf/

它可用于定义枚举值的本地化翻译,同时定义它们的顺序。此外,它还为 ComboBoxes 提供了一个 ItemsSource,它允许在设置类型化枚举值的同时选择本地化的字符串项。

它是用 VB.net 编写的,但您应该可以轻松地将其转换为 C#,或者您可以使用包含二进制形式的库。

于 2012-09-18T17:18:40.760 回答