友好的返回中文枚举

1 阅读3分钟

WPF中的TypeConverter技巧

问题背景

在开发WPF应用时,我们经常会遇到需要在界面上显示枚举值的情况。默认情况下,枚举值会显示为其英文名称,这对于中文用户来说不够友好。例如,我们定义了一个简单的枚举:

public enum Active
{
    IsEnable, IsDisable
}

当我们在界面上绑定这个枚举时,会显示"IsEnable"或"IsDisable",而不是用户期望的"已启用"或"已禁用"。

解决方案:自定义TypeConverter

WPF提供了TypeConverter机制,可以帮助我们实现枚举值的友好显示。下面是一个完整的实现示例:

public class ActiveTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }
​
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string str)
        {
            // 根据友好名称反解析回枚举
            foreach (var field in typeof(Active).GetFields())
            {
                var desc = field.GetCustomAttribute<DescriptionAttribute>();
                if (desc != null && desc.Description == str)
                    return (Active)field.GetValue(null);
​
                if (field.Name == str)
                    return Enum.Parse(typeof(Active), str);
            }
        }
        return base.ConvertFrom(context, culture, value);
    }
​
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string) && value is Active active)
        {
            var field = typeof(Active).GetField(active.ToString());
            var desc = field?.GetCustomAttribute<DescriptionAttribute>();
            if (desc != null)
                return desc.Description; // 返回友好名称
            return active.ToString();
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}
​
// 使用自定义转换器
[TypeConverter(typeof(ActiveTypeConverter))]
public enum Active
{
    [Description("已启用")]
    IsEnable,
    [Description("已禁用")]
    IsDisable
}

原理分析

TypeConverter vs IValueConverter

在WPF中,有两种主要的转换机制:

特性TypeConverter (类型转换器)IValueConverter (值转换器)
应用场景主要在XAML解析时,将特性(Attribute)里的字符串转换成对象的属性值主要在数据绑定时,在源(Source)和目标(Target)之间进行数据转换
定义位置通过[TypeConverterAttribute]标记在类型上通过Converter属性在绑定(Binding)中显式引用
作用时机XAML编译或加载时数据绑定的值发生变化时
是否自动是。对于很多基础类型,WPF会自动调用其关联的TypeConverter否。必须手动在Binding中指定Converter={StaticResource ...}

枚举的默认TypeConverter

枚举类型本身就关联了一个默认的EnumConverter,这是WPF能够自动在枚举和字符串之间转换的原因:

  1. TypeDescriptor机制:TypeDescriptor会为枚举类型动态获取TypeConverter
  2. EnumConverter的注册:通过类型描述器的元数据机制关联到所有枚举类型
  3. 自动转换:EnumConverter默认支持Enum.Parse和ToString()转换

完整的调用链

当我们在XAML中绑定枚举属性时,整个流程如下:

  1. XAML绑定:Text="{Binding MyEnumProperty}"
  2. WPF绑定引擎发现目标(string) vs 源(enum)
  3. 调用 TypeDescriptor.GetConverter(typeof(MyEnum))
  4. TypeDescriptor通过内部机制为枚举返回EnumConverter
  5. 使用EnumConverter.ConvertFrom/ConvertTo进行转换
  6. 完成string ↔ enum的互转

实际应用

下面是一个完整的WPF应用示例,展示了如何使用自定义TypeConverter:

XAML代码

<Window.Resources>
    <conver:StringToVisibilityConverter x:Key="StringToVisibilityConverter"/>
</Window.Resources><Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="30"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <!-- 第一个布局容器 -->
    <Grid x:Name="Layout1" Visibility="{Binding CurrentFlyoutActive, Converter={StaticResource StringToVisibilityConverter}, ConverterParameter=IsDisable}">
        <StackPanel Background="Red"></StackPanel>
    </Grid>
​
    <!-- 第二个布局容器 -->
    <Grid x:Name="Layout2" Visibility="{Binding CurrentFlyoutActive, Converter={StaticResource StringToVisibilityConverter}, ConverterParameter=IsEnable}">
        <StackPanel Background="Green"></StackPanel>
    </Grid>
​
    <!-- 切换按钮 -->
    <Button Grid.Row="1" Content="切换界面" Command="{s:Action ExecuteNextPage}"/>
​
    <TextBox Grid.Row="2" Text="{Binding CurrentFlyoutActive}"/>
</Grid>

ViewModel代码

public class ShellViewModel : Screen
{
    private Active _currentFlyoutActive;
​
    public Active CurrentFlyoutActive
    {
        get => _currentFlyoutActive;
        set => SetAndNotify(ref _currentFlyoutActive, value);
    }
​
​
    public void ExecuteNextPage()
    {
        CurrentFlyoutActive = CurrentFlyoutActive == Active.IsDisable ? Active.IsEnable : Active.IsDisable;
    }
}

扩展思考

  1. 多语言支持:可以基于CultureInfo实现多语言版本的枚举显示
  2. 更复杂的转换逻辑:除了DescriptionAttribute,还可以使用自定义属性实现更复杂的转换逻辑
  3. 性能优化:对于频繁使用的枚举,可以考虑缓存转换结果
  4. 通用解决方案:可以创建一个通用的EnumDescriptionConverter,适用于所有带DescriptionAttribute的枚举

总结

通过自定义TypeConverter,我们可以实现枚举值的友好显示,提升用户体验。这种方法不仅适用于WPF,也适用于其他使用TypeConverter机制的.NET框架。

TypeConverter是WPF中一个强大的特性,它为我们提供了一种声明式的方式来处理类型转换,使代码更加简洁和可维护。当你需要在界面上显示友好的枚举值时,不妨考虑使用这种方法。