在WPF应用程序开发中,经常需要处理各种数据绑定的场景,尤其是涉及到枚举类型的数据。当枚举类型可以为空时,就需要一种方法来处理这种情况。本文将介绍如何在WPF中使用ComboBox控件与可空枚举进行绑定,并展示相关的XAML和C#代码实现。
首先,需要创建一个ViewModel,它将作为数据模型。在这个ViewModel中,将定义一个可空的枚举类型的属性。
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
    public enum ProductType
    {
        [Description("湿粮")]
        WetFood = 1,
        [Description("干粮")]
        DryFood = 2
    }
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private ProductType? selectedProductType;
        public ProductType? SelectedProductType
        {
            get { return selectedProductType; }
            set
            {
                selectedProductType = value;
                OnPropertyChanged();
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(
            [CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 
        }
    }
}
    
在上面的代码中,定义了一个名为ProductType的枚举,它有两个成员:WetFood和DryFood。还定义了一个MainWindowViewModel类,它实现了INotifyPropertyChanged接口,以便在属性值发生变化时通知UI进行更新。SelectedProductType属性是一个可空的ProductType枚举类型。
接下来,需要在XAML中定义UI。将使用ComboBox控件来显示枚举类型的值,并允许用户选择。
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <ObjectDataProvider x:Key="ProductTypeEnumProvider" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:ProductType" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid>
        <ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" SelectedItem="{Binding SelectedProductType, Converter={x:Static local:NullableEnumConverter.Instance}, ConverterParameter={x:Static local:ProductType.DryFood}}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=., Mode=OneWay, Converter={x:Static local:NullableEnumToFriendlyNameConverter.Instance}}" Height="Auto" Margin="0" VerticalAlignment="Center" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
            <ComboBox.ItemsSource>
                <CompositeCollection>
                    <x:Static Member="local:NullHelper.NullComboStringValue" />
                    <CollectionContainer Collection="{Binding Source={StaticResource ProductTypeEnumProvider}}" />
                </CompositeCollection>
            </ComboBox.ItemsSource>
        </ComboBox>
    </Grid>
</Window>
    
在上面的XAML代码中,定义了一个ComboBox控件,并使用了一个ObjectDataProvider来获取枚举类型的所有值。还定义了一个ItemTemplate,用于显示每个枚举值的友好名称。ComboBox的ItemsSource属性被设置为一个CompositeCollection,它允许将不同的数据源组合在一起。
为了使ComboBox能够正确显示枚举值的友好名称,需要定义一些辅助类和值转换器。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WpfApplication1
{
    public class NullHelper
    {
        public static string NullComboStringValue
        {
            get { return "无"; }
        }
    }
}
    
NullHelper类提供了一个静态属性NullComboStringValue,它返回一个字符串"无",用于表示ComboBox中的空选项。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Data;
namespace WpfApplication1
{
    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);
        }
    }
}
    
NullableEnumConverter类实现了IValueConverter接口,用于将可空枚举值转换为友好名称。如果枚举值为空,则返回NullHelper类中定义的字符串。
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows.Data;
namespace WpfApplication1
{
    [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 = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
                    return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
                }
            }
            return NullHelper.NullComboStringValue;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new Exception("Can't convert back");
        }
    }
}