WPF MVVM binding enumeration

introduction

When you use WPF application development, do you need to bind data to Enum data? In this article, I will show you how to handle Enum data binding in WPF.

It is assumed that there is such a definition of Enum data, as shown in the following code:

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

1, Common processing methods in WPF

1.1 adding references

Introduce the namespace System from mscorlib in the MainWindow.xaml file.

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

1.2 create an ObjectDataProvider resource

In this step, you need to create a resource of ObjectDataProvider and give it a key name x:Key="DataFromEnum", so that you can use DataFromEnum in your code. And you need to set MethodName to GetValues existing on Enum type, and then set ObjectType to Enum type. Next, you will need to set the Enum type of ObjectDataProvider.MethodParameters. Finally, the ObjectDataProvider resource you added is shown in the following code

    <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 data processing

Now you can use data binding. For example, if you want to bind data to ComboBox, you need to set ItemSource as a new binding and bind the data source to the resource named DataFromEnum defined above.

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

So far, all processing has been completed, and the running program can see that the data has been correctly bound to the ComboBox.

2, Better treatment method

Let's look at how WPF features can be used to improve code usage and readability when data binding Enum types. First, you want to encapsulate the binding of Enum type without the logical processing of ObjectDataProvider resources. You also want to avoid having to define resources to use the binding function in xaml. Ideally, you should inline everything just like you would with a normal object binding. To do this, you need to use the help class of the custom MarkupExtension. This extension will simply accept the Enum type, and then create a list of binding Enum values for the control. This implementation is actually very simple.

2.1 MarkupExtension help class

MarkupExtension help class is defined as follows:

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 data processing

    <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>

Take a look at the running results:

3, Extension: add description support for Enum type

Now we can bind Enum type without using ObjectDataProvider resource. Compare the two methods. This new method will make you refreshing in detail, like discovering a new continent.

Enum type values are generally used in programs. In order to give users a better use experience, the attribute: Description description is usually added in front of the enumeration value. To do this, we just need to use TypeConverter for conversion.

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);
        }
    }
}

Then add the [Description] attribute to the defined enumeration value

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
    }
}

Program running results:

As you can see, when we add the [Description] property, both methods can bind the value of the [Description] property to the specified control.

Tags: WPF

Posted on Fri, 24 Sep 2021 11:43:18 -0400 by jsladek