【问题标题】:IValueConverter from string来自字符串的 IValueConverter
【发布时间】:2012-09-10 20:36:34
【问题描述】:

我有一个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 生成,无法更改。

【问题讨论】:

  • 请出示UserTypesToStringConverter的代码
  • 能否发一下UserTypesToStringConverter的相关代码。
  • 您是否尝试过使用附属程序集使用 WPF 本地化?然后你不需要在代码中做任何本地化工作。 msdn.microsoft.com/en-us/library/ms788718.aspx - 你可以给这些标签控件一个 UID 和你的枚举的 ToString 值来解决这个特定的问题。
  • @MerlynMorgan-Graham 正如我所说,我问这个问题不是为了本地化问题。我想知道这种事情是怎么做到的。当您有大量枚举并希望用户选择值时;值需要“用户友好” - 修改、在单词之间添加空格等。
  • 获取空格的一种常见方法是在枚举名称中使用下划线,然后在返回空格之前替换它们。作为一个选项,您还可以查看 Codeplex 上的 SpecializedEnum。它完成了枚举的大部分工作,但允许将任意类型用于值,这可能对此有用。 specializedenum.codeplex.com

标签: c# wpf binding datatemplate ivalueconverter


【解决方案1】:

是的,当您将值传递给转换器时,它将成为 string 作为 Enum (EnumConverter) 的默认类型转换器,用于 GetStandardValues(即 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 本地化工具(例如卫星程序集)

【讨论】:

  • 这看起来很棒并且有效。现在,我有一个问题。枚举是从 ADO.NET 实体图生成的,所以我无法更改它:/
  • 快速的 Google 搜索显示了一些讨论 ADO.NET 与自定义对象和提供程序的文章。我没有读得太远,因为我还有其他工作要做。 ...在我们的应用程序中,我们不直接使用实体对象,而是始终将它们转换为我们的提供者层中的本地类型,因为我们发现 ADO.NET 对象具有不必要的更改跟踪开销,而一旦我们拥有数据,我们就不需要这些开销.
  • 我相信我至少给了您一个部分解决方案(如果考虑到您在原始问题中没有提到 ADO.NET,则为完整解决方案)。您应该能够接受上述内容并使其适合您的需求。
【解决方案2】:

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

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_}" />

【讨论】:

    【解决方案3】:

    编辑:

    您可以简单地在视图模型上添加一个返回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]
    }
    

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-25
        • 2011-09-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多