在开发WPF应用程序时,经常需要使用ComboBox控件来允许用户从一组预定义的选项中进行选择。有时,希望这些选项对应于枚举值,并且希望显示给用户的是枚举值的友好名称,而不是实际的枚举值。此外,可能还希望允许用户不进行任何选择,即支持空(null)选择。本文将介绍如何在不污染枚举定义的情况下,实现这样的ComboBox控件。
定义枚举和ViewModel
首先,定义一个枚举类型,用于表示可选的动物,并使用EnumMemberAttribute来指定每个枚举值的友好名称。
public enum Animals
{
[EnumMember(Value = "它是一只可爱的小猫")]
Cat = 1,
[EnumMember(Value = "它是一只活泼的小狗")]
Dog = 2
}
public class MainWindowViewModel : INPCBase
{
private Animals? selectedAnimal;
public MainWindowViewModel()
{
SelectedAnimal = null;
}
public Animals? SelectedAnimal
{
get { return this.selectedAnimal; }
set
{
RaiseAndSetIfChanged(ref this.selectedAnimal, value, () => this.SelectedAnimal);
MessageBox.Show("SelectedAnimal: " + (!selectedAnimal.HasValue ? NullHelper.NullComboStringValue : selectedAnimal.ToString()));
}
}
}
在ViewModel中,定义了一个可空的枚举类型的SelectedAnimal属性。当属性值改变时,通过MessageBox显示当前选中的动物,如果用户没有选择任何动物,则显示一个友好的提示信息。
定义View
接下来,定义XAML视图。使用ObjectDataProvider来提供枚举值,使用CompositeCollection来组合枚举值和一个特殊的"Null"值。同时,使用自定义的NullableEnumToFriendlyNameConverter来转换枚举值到友好名称,使用NullableEnumConverter来将用户的选择转换回枚举值或null。
<Window x:Class="NullEnumCombo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:nullEnumCombo="clr-namespace:NullEnumCombo"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="animalTypeFromEnum" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="nullEnumCombo:Animals" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<ComboBox Width="150" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center"
SelectedItem="{Binding SelectedAnimal,
Converter={x:Static nullEnumCombo:NullableEnumConverter.Instance},
ConverterParameter={x:Static nullEnumCombo:Animals.Cat}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Mode=OneWay,
Converter={x:Static nullEnumCombo:NullableEnumToFriendlyNameConverter.Instance}}"
Height="Auto" Margin="0" VerticalAlignment="Center" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemsSource>
<CompositeCollection>
<x:Static Member="nullEnumCombo:NullHelper.NullComboStringValue" />
<CollectionContainer Collection="{Binding Source={StaticResource animalTypeFromEnum}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</Window>
在XAML中,定义了一个ComboBox控件,它的ItemsSource绑定到一个CompositeCollection,其中包含了一个特殊的"Null"值和枚举值。ItemTemplate定义了ComboBox项的显示方式,使用NullableEnumToFriendlyNameConverter将枚举值转换为友好名称。SelectedItem绑定到ViewModel的SelectedAnimal属性,并使用NullableEnumConverter进行转换。
定义值转换器和辅助类
为了实现枚举值到友好名称的转换,定义了NullableEnumToFriendlyNameConverter值转换器。同时,为了将用户的选择转换回枚举值或null,定义了NullableEnumConverter值转换器。此外,定义了一个辅助类NullHelper,用于提供"Null"值的友好名称。
public class NullableEnumConverter : IValueConverter
{
private NullableEnumConverter() { }
static NullableEnumConverter()
{
Instance = new NullableEnumConverter();
}
public static NullableEnumConverter Instance { get; private set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return NullHelper.NullComboStringValue;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Type enumType = parameter.GetType();
if (value.ToString().Equals(NullHelper.NullComboStringValue))
{
return null;
}
object rawEnum = Enum.Parse(enumType, value.ToString());
return System.Convert.ChangeType(rawEnum, enumType);
}
}
[ValueConversion(typeof(object), typeof(String))]
public class NullableEnumToFriendlyNameConverter : IValueConverter
{
private NullableEnumToFriendlyNameConverter() { }
static NullableEnumToFriendlyNameConverter()
{
Instance = new NullableEnumToFriendlyNameConverter();
}
public static NullableEnumToFriendlyNameConverter Instance { get; private set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && !string.IsNullOrEmpty(value.ToString()) && !value.ToString().Equals(NullHelper.NullComboStringValue))
{
FieldInfo fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
var attributes = (EnumMemberAttribute[])fi.GetCustomAttributes(typeof(EnumMemberAttribute), false);
return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Value))) ? attributes[0].Value : value.ToString();
}
}
return NullHelper.NullComboStringValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new Exception("Can't convert back");
}
}
public class NullHelper
{
public static string NullComboStringValue
{
get { return "(无)"; }
}
}