在WPF中一種較好的繫結Enums資料方法

芝麻麻雀發表於2020-10-26

引言

在你使用wpf應用程式開發的時候,是否需要進行資料繫結到Enum資料呢?在這篇文章中,我將向你展示在WPF中處理Enum資料繫結的方法。

假設存在一個這樣的Enum資料的定義,具體內容如下文程式碼中所示:

namespace LocalizeFrameworkWpfApp
{
    public enum Status
    {
        Horrible,
        Bad,
        SoSo,
        Good,
        Better,
        Best
    }
}

一、WPF中的通常處理方法

1.1 新增引用

MainWindow.xaml檔案中從mscorlib中引入名稱空間System

xmlns:sys="clr-namespace:System;assembly=mscorlib"

1.2 建立一個ObjectDataProvider資源

在此步驟中,你需要建立一個ObjectDataProvider的資源,並給它一個鍵名x:Key="DataFromEnum",這樣就可以使用DataFromEnum在程式碼中使用它。並且你需要給MethodName設定為Enum型別上存在的GetValues,然後將ObjectType設定為Enum型別。接下來,你將需設定ObjectDataProvider.MethodParametersEnum型別。最後,你新增的ObjectDataProvider資源如下面程式碼所示

    <Window.Resources>
        <ObjectDataProvider
            x:Key="DataFromEnum"
            MethodName="GetValues"
            ObjectType="{x:Type sys:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:Status">
                </x:Type>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>

1.3 Binding資料處理

現在,你可以使用資料繫結了。例如,想將資料繫結到ComboBox上面,那麼你需要設定ItemSource為一個新的繫結,並將資料來源繫結到我們上面定義的名為DataFromEnum的資源。

    <Grid>
        <ComboBox
            MinWidth="150"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            ItemsSource="{Binding Source={StaticResource DataFromEnum}}">
        </ComboBox>
    </Grid>

到現在為止,所有的已經處理完成,執行程式可以看到資料已經正確繫結到ComboBox上面。

二、較好的處理方法

讓我們來看看當資料繫結Enum型別時,如何使用WPF特性來改進程式碼的使用和可讀性。首先,想封裝Enum型別的繫結而不需要ObjectDataProvider資源的邏輯處理,還希望不需要必須定義資源才能在xaml中使用繫結功能。理想情況下,應該像處理普通物件的繫結一樣,將所有內容都內聯處理。為此,需要利用定製MarkupExtension的幫助類。這個擴充套件將簡單的接受Enum型別,然後為控制元件建立一個可繫結Enum值的列表,這種實現其實很簡單。

2.1 MarkupExtension幫助類

MarkupExtension幫助類定義如下:

namespace LocalizeFrameworkWpfApp
{
    public class EnumBindingSourceExtension:MarkupExtension
    {
        private Type _enumType;

        public Type EnumType
        {
            get { return _enumType; }
            set
            {
                if (value != _enumType)
                {
                    if (null != value)
                    {
                        var enumType = Nullable.GetUnderlyingType(value) ?? value;
                        if (!enumType.IsEnum)
                        {
                            throw new ArgumentException("Type must bu for an Enum");
                        }

                    }

                    _enumType = value;
                }
            }
        }

        public EnumBindingSourceExtension()
        {
            
        }

        public EnumBindingSourceExtension(Type enumType)
        {
            EnumType = enumType;
        }
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (null == _enumType)
            {
                throw  new InvalidOperationException("The EnumTYpe must be specified.");
            }

            var actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType;
            var enumValues = Enum.GetValues(actualEnumType);

            if (actualEnumType == _enumType)
            {
                return enumValues;
            }

            var tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
            enumValues.CopyTo(tempArray, 1);

            return tempArray;
        }
    }
}

2.2 Binding資料處理

    <Grid>
        <StackPanel>
            <ComboBox
                MinWidth="150"
                HorizontalAlignment="Center"
                ItemsSource="{Binding Source={StaticResource DataFromEnum}}">
            </ComboBox>
            <ComboBox
                MinWidth="150"
                HorizontalAlignment="Center"
                ItemsSource="{Binding Source={local:EnumBindingSource {x:Type local:Status}}}">
            </ComboBox>
        </StackPanel>
    </Grid>

看一下執行結果:

三、擴充套件:新增Enum型別的描述(Description)支援

現在我們可以不用使用ObjectDataProvider資源進行Enum型別的繫結工作了。這兩種方法進行對比一下,詳細這個新方法會讓你耳目一新,像發現了新大陸一般。

Enum型別的值一般使用在程式中,而為了讓使用者獲得更好的使用體驗,一般都會在列舉值前面新增上屬性:Description描述。為了完成此工作,我們只需使用TypeConverter進行轉換。

namespace LocalizeFrameworkWpfApp
{
    public class EnumDescriptionTypeConverter:EnumConverter
    {
        public EnumDescriptionTypeConverter(Type type) : base(type)
        {
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                if (null != value)
                {
                    FieldInfo fi = value.GetType().GetField(value.ToString());

                    if (null != fi)
                    {
                        var attributes =
                            (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

                        return ((attributes.Length > 0) && (!string.IsNullOrEmpty(attributes[0].Description)))
                            ? attributes[0].Description
                            : value.ToString();
                    }
                }

                return string.Empty;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
}

然後對定義的列舉值新增上[Description]屬性

namespace LocalizeFrameworkWpfApp
{
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    public enum Status
    {
        [Description("This is horrible")]
        Horrible,
        [Description("This is Bad")]
        Bad,
        [Description("This is SoSo")]
        SoSo,
        [Description("This is Good")]
        Good,
        [Description("This is Better")]
        Better,
        [Description("This is Best")]
        Best
    }
}

程式執行結果:

可以看到,我們新增了[Description]屬性時,這兩種方法都可以將[Description]屬性的值繫結到指定控制元件中。

如果你覺得不錯,掃描下面公眾號給個關注,在此感謝!!

相關文章