In our WPF application we wanted to bind ComboBox and ListBox controls to enums. The WPF way to set up such a binding is described here. This solution works absolutely fine but it requires the definition of an ObjectDataProvider for every enum. This in turn leads to lower maintainability and the code is not reusable. Due to these disadvantages we searched for an easier, resuable and more maintainable solution. During my internet research I found a blog post written by Brian Lagunas that describes another approach to bind enums in WPF. Brians approach met exactly our expectations so we decided to apply it in our WPF project.

The following briefly describes how we bound ComboBox and ListBox controls to enums.

Let’s assume we have the following enum.

ArbitraryEnum.cs

/**
 * Copyright 2017 d-fens GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.ComponentModel;

namespace biz.dfch.CS.ArbitraryWpfApplication.UI.Domain.Arbitrary
{
    public enum ArbitraryEnum
    {
        ValueOne,
        ValueTwo,
        ValueTree,
        ValueFour
    }
}

To eliminate the need of ObjectDataProviders a custom MarkupExtension class has to be implemented. The ProvideValue method of the custom MarkupExtension class is responsible to create and return a list containing all enum values of the specified enum type.

EnumBindingSourceExtension.cs

/**
 * Copyright 2017 d-fens GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Diagnostics.Contracts;
using System.Windows.Markup;

namespace biz.dfch.CS.ArbitraryWpfApplication.UI.Extensions
{
    public class EnumBindingSourceExtension : MarkupExtension
    {
        private Type _enumType;
        public Type EnumType
        {
            get
            {
                return _enumType;
            }
            set
            {
                Contract.Assert(null != value);

                if (value != _enumType)
                {
                    var enumType = Nullable.GetUnderlyingType(value) ?? value;
                    Contract.Assert(enumType.IsEnum);

                    _enumType = value;
                }
            }
        }

        public EnumBindingSourceExtension()
        {
            // N/A
        }

        public EnumBindingSourceExtension(Type enumType)
        {
            Contract.Requires(null != enumType);

            EnumType = enumType;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            Contract.Assert(null != _enumType);

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

Instead of specifying a predefined static ObjectDataProvider resource as source in binding expression of ComboBox or ListBox control, we now assign a source of type EnumBindingSource and specify the corresponding enum type in binding expression.

ItemsSource="{Binding Source={ext:EnumBindingSource {x:Type sew:ArbitraryEnum}}}"

Important

The following namespaces have to be added in XAML:

xmlns:sew="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Domain.Arbitrary"
xmlns:gl="clr-namespace:System.Globalization;assembly=mscorlib"

Home.xaml

<Page x:Class="biz.dfch.CS.ArbitraryWpfApplication.UI.Pages.Home"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:vm="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.ViewModels"
      xmlns:p="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Properties"
      xmlns:ext="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Extensions"
      xmlns:sew="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Domain.Arbitrary"
      xmlns:gl="clr-namespace:System.Globalization;assembly=mscorlib"
      mc:Ignorable="d"

      Title="{x:Static p:Resources.Page_Home_Title}" >

    <Page.DataContext>
        <vm:HomeViewModel></vm:HomeViewModel>
    </Page.DataContext>

    <DockPanel>
        <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0" Content="{x:Static p:Resources.Page_Home_Label__Arbitrary}"/>
            <ComboBox Grid.Row="0" Grid.Column="1" MinWidth="200" HorizontalAlignment="Left" VerticalAlignment="Center" ItemsSource="{Binding Source={ext:EnumBindingSource {x:Type sew:ArbitraryEnum}}}" SelectedItem="{Binding ArbitraryViewModelProperty, Mode=TwoWay}" />
        </Grid>
    </DockPanel>
</Page>

For the sake of completeness the corresponding ViewModel.

HomeViewModel.cs

/**
 * Copyright 2017 d-fens GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.ComponentModel.DataAnnotations;
using biz.dfch.CS.ArbitraryWpfApplication.UI.Constants;
using biz.dfch.CS.ArbitraryWpfApplication.UI.Domain.Arbitrary;

namespace biz.dfch.CS.ArbitraryWpfApplication.UI.ViewModels
{
    public class HomeViewModel : ViewModelBase
    {
        #region Properties

        [Required]
        private ArbitraryEnum _arbitraryViewModelProperty;
        public ArbitraryEnum ArbitraryViewModelProperty
        {
            get { return _arbitraryViewModelProperty; }
            set
            {
                _arbitraryViewModelProperty = value;
                RaisePropertyChangedEvent(nameof(ArbitraryViewModelProperty));
            }
        }

        #endregion Properties
    }
}

Thanks to Brian Lagunas for his great blog post about A Better Way to Data Bind Enums in WPF!

« WPF Series -4- MahApps.Metro and MahApps.Metro.Resources WPF Series -6- Globalization of Enums »

3 Comments »

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.