如何将枚举绑定到WPF中的ComboBox

枚举对于定义值列表非常有用。当这些值要在图形界面中显示时,很快就会意识到技术名称和要显示的名称是不同的。此外,有时必须将界面翻译成多种语言。因此,您需要一种机制来解决这一问题。

让我们从定义枚举开始:

public enum Week
{
    [Display(ResourceType = typeof (Resources), Name = "TestEnum_First")]
    First,

    [Display(ResourceType = typeof(Resources), Name = "TestEnum_Second")]
    Second,

    [Display(ResourceType = typeof(Resources), Name = "TestEnum_Third")]
    Third,

    [Display(ResourceType = typeof(Resources), Name = "TestEnum_Fourth")]
    Fourth,

    [Display(ResourceType = typeof(Resources), Name = "TestEnum_Last")]
    Last
}

对于每个值,我们使用[Display]属性定义一个自定义名称。通过此属性,可以定义直接显示或通过资源显示的名称。然后可以使用反射来恢复此属性的值:

Array enumValues = type.GetEnumValues();
foreach (object enumValue in enumValues)
{
    var fieldInfo = type.GetField(enumValue.ToString());
    DisplayAttribute displayAttribute = fieldInfo.GetCustomAttribute<DisplayAttribute>();
    if (displayAttribute != null)
    {
        string name = displayAttribute.GetName();
    }
    else
    {
        string name = enumValue.ToString();
    }
}

现在让我们看一下WPF的功能:

<TextBlock Text="{Binding Source={x:Static demo:Week.Second}}" />

没有奇迹,显示的值是“第二”。我们将编写一个转换器,使用该Display属性将值转换为文本。

[ValueConversion(typeof(Enum), typeof(string))]
public class EnumValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var enumValue = value as Enum;
        if (enumValue != null)
        {
            // see the full code here: https://gist.github.com/meziantou/90730189693205fbf9d0
            return LocalizationUtilities.GetEnumMemberLocalization(enumValue);
        }
        return string.Format("{0}", value);
    }

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

我们可以在XAML中使用此Converter:

<Window.Resources>
    <demo:EnumValueConverter x:Key="EnumValueConverter"/>
</Window.Resources>

<TextBlock Text="{Binding Source={x:Static demo:Week.Second}, Converter={StaticResource EnumValueConverter}}" />

我们已经处理了单个值的情况。另一个常见的需求是在ComboBox中显示枚举值的列表。MSDN告诉我们如何使用ObjectDataProvider调用XAML中的方法Enum.GetValues(typeof(Week))

<Window.Resources>
    <demo:EnumValueConverter x:Key="EnumValueConverter" />
    <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="WeekDataProvider">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="demo:Week" />
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

<ComboBox ItemsSource="{Binding Source={StaticResource WeekDataProvider}}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource EnumValueConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

 

 

我们走到了尽头,但这很罗word(13行)。为简化起见,您可以创建一个DataProvider而不是ObjectDataProvider上面的来获取一些行,但是我们可以做得更好😉

让我们使用标记扩展来简化代码

标记扩展经常在不知道其含义的情况下使用。其实每次使用时间{Binding}{StaticResource}或者{x:Type},您可以使用MarkupExtension。MarkupExtensions的目的是允许您表达xml只不允许表达的内容。例如,用xaml表示空值很复杂,因此{x:Null}。它们还使表达复杂的事物变得容易,并减少了我们代码的冗长性。可以想象,您可以编写自己的MarkupExtension:

[MarkupExtensionReturnType(typeof(IEnumerable<LocalizedValue>)]
public class EnumExtension : MarkupExtension
{
    public EnumExtension()
    {
    }

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

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

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

        return LocalizationUtilities.GetEnumLocalization(EnumType);
    }
}

现在您可以使用它:

<ComboBox ItemsSource="{demo:Enum demo:Week}" SelectedValuePath="Value" SelectedValue="{Binding MyEnumProperty}" />

SelectedValuePath是类的Value属性LocalizedValue。对于DataGridXAML 的列来说,是相似的:

<DataGrid>
  <DataGrid.Columns>
    <DataGridComboBoxColumn ItemsSource="{demo:Enum demo:Week}" SelectedValuePath="Value" SelectedValueBinding="{Binding MyEnumProperty}" />
  </DataGrid.Columns>
</DataGrid>

多亏了MarkupExtension😃 ,XAML代码的数量已大大减少(从1行而不是13行)

非本地枚举的简化标记扩展

如果不需要本地化枚举值,则可以将代码简化如下:

[MarkupExtensionReturnType(typeof(IEnumerable<Enum>))]
public sealed class EnumValuesExtension : MarkupExtension
{
    public EnumValuesExtension() { }
    public EnumValuesExtension(Type enumType) => EnumType = enumType;

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

    public override object ProvideValue(IServiceProvider serviceProvider) => Enum.GetValues(EnumType);
}
<ComboBox ItemsSource="{meziantou:EnumValues local:Week}" />

使用Meziantou.Framework.WPF

您可以使用以下软件包来代替复制以前的Markup扩展Meziantou.Framework.WPF

<Project>
    ...
    <ItemGroup>
        <PackageReference Include="Meziantou.Framework.WPF" Version="1.3.0" />
    </ItemGroup>
    ...
</Project>
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:meziantou="clr-namespace:Meziantou.Framework.WPF;assembly=Meziantou.Framework.WPF"
        Title="MainWindow" Height="450" Width="800">
        <StackPanel>
            <ComboBox ItemsSource="{meziantou:EnumValues local:Week}" />
            <ComboBox ItemsSource="{meziantou:LocalizedEnumValues local:Week}" SelectedValuePath="Value" />
        </StackPanel>
</Window>

其他资源

您对此帖子有任何疑问或建议吗?联络我!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值