wpf 来自字符串的 IValueConverter

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12479409/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 05:24:10  来源:igfitidea点击:

IValueConverter from string

c#wpfbindingdatatemplateivalueconverter

提问by Aleksandar Toplek

I have an Enumthat needs to be shown in ComboBox. I have managed to get enum values to combobox using ItemsSourceand I'm trying to localize them. I thought that that could be done using value converter but as my enum values are already strings compiler throws error that IValueConvertercan't take string as input. I'm not aware of any other way to convert them to other string value. Is there some other way to do that (not the localization but conversion)?

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

I'm using this marku extension to get enum values

我正在使用这个 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);
    }
}

and in Window.xaml

并在 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>

And here is converter class, it's just a test class, no localization yet.

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

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);
     }
}

-- EDIT --

- 编辑 -

Enum is generated by ADO.NET Diagram and can't be changed.

Enum 由 ADO.NET Diagram 生成,无法更改。

回答by Dennis

Yes, by the time you pass the value into the converter it will be a stringas the default type converter for Enum (EnumConverter) for GetStandardValues(i.e. Enum.GetValues()) returns an enumerable of the fields as strings.

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

The best way to solve this to write a custom type converter to decorate your Enums with. Fortunately you are not the first person that has needed to this, see below for code sample.

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

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);
    }

}

Usage

用法

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

As you can see, the above enum we have used the Descriptionattribute to decorate each field with a user friend description and have overridden the type converter to first look for this attribute.

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

Not 100% but to get this to work with your code, you will also need to change your MarkupExtensionto be the following (Note: I have not tested this, so some work on your part is required).

不是 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);
    }
}

Also, I have only done limited localisation for an application however I believe this is the best and most maintainable approach as will be able to leverage the existing .NET localisation tools (e.g. satellite assemblies)

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

回答by Merlyn Morgan-Graham

Edit:

编辑:

You could simply add a property on your view model that returns MyEnumType[]and simply return MyEnumType.GetValues(). Then at the point of data binding you would have enum values to reference instead of string values.

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

The helper class you specified seems clean upon first look, but it really isn't - there is a lot of cruft involved, and you're making decisions in the view that might be better left to the view model. Depending on how you look at the problem, it makes sense to expose the enum values in the view model. The allowed values (even if it is all of them) can be seen as an aspect of the business logic and not an aspect of the view.

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

The rest of my answer might help you still if this isn't quite enough to solve the problem.

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



Before edit:

编辑前:

You could solve this problem by simply using string resources, except that resource keys are typically hard-coded in the view.

您可以通过简单地使用字符串资源来解决这个问题,但资源键通常是在视图中硬编码的。

So I looked into whether there is a way to dynamically bind string resource keys, and found these other answers:

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

The second of these seems like a clean and simple option.

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

While I was looking, I also found this blog post:

在寻找的过程中,我还发现了这篇博文:

Here's the code from their sample.

这是他们示例中的代码。

View:

看法:

<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>

Resources:

资源:

<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>

Markup extension to enable string resource key binding:

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

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]
}

回答by Thomas Levesque

I use a generic resource converter to do this. You just need to specify the resource manager to use, and pass a prefix as the converter parameter:

我使用通用资源转换器来做到这一点。您只需要指定要使用的资源管理器,并传递一个前缀作为转换器参数:

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();
    }
}

Assuming you have an enum like this:

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

class MyEnum
{
    Foo,
    Bar,
    Baz
}

And resources named MyEnum_Foo, MyEnum_Bar and MyEnum_Baz, you can use it like this:

以及名为 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_}" />

回答by hbarck

You might be interested in the LocalizedList Class which is described in the following blog post: http://wpfglue.wordpress.com/2010/01/14/localized-value-formatting-in-wpf/

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

It can be used to define a localized translation for enum values, at the same time defining their order. Also, it provides an ItemsSource for ComboBoxes which allows selecting localized string items while setting typed enum values.

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

It's written in VB.net, but you should easily be able to translate it to C#, or you could use the library which contains it in binary form.

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