Search code examples
wpfdata-bindingenumscombobox

How to bind a wpf Combobox to an Enum type and display it in the Combobox as a Description where the Enum is defined?


public enum IVAC_VT_SHIFT_SPD
{
    [Description("16.1299991607666")]
    DATA16,
    [Description("32.130001068115234")]
    DATA32,
}
<ComboBox Grid.Row="8"
          Grid.Column="2"
          Width="194"
          Height="40"
          Margin="0,0,3,0"
          HorizontalAlignment="Right"
          VerticalContentAlignment="Center"
          ItemsSource="{Binding Source={StaticResource IVAC_VT_SHIFT_SPD}}"
          SelectedValue="{Binding VTShiftSPD}" />

<ObjectDataProvider x:Key="IVAC_VT_SHIFT_SPD"
                    MethodName="GetValues"
                    ObjectType="{x:Type System:Enum}">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="enm:IVAC_VT_SHIFT_SPD" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
private IVAC_VT_SHIFT_SPD _VTShiftSPD = IVAC_VT_SHIFT_SPD.DATA16;
public IVAC_VT_SHIFT_SPD VTShiftSPD
{
    get => _VTShiftSPD;
    set
    {
        _VTShiftSPD = value;
        OnPropertyChanged(nameof(VTShiftSPD));
    }
}

What I want is to bind the enum type value to the Combobox. However, I want the contents of the Combobox to be displayed as the contents of the Description. The selection must be selected as an enum.

  1. Is there a way to do it without using a converter?
  2. Is there a way to get it done only in Xaml?
  3. I want a simple method.

Solution

  • I think you can get pretty close accomplishing what you are looking for. I did use a couple C# classes to accomplish it, but those will work for any future enums without you needing to constantly create additional classes or code behind to facilitate getting the descriptions in the dropdown.

    In my sample, I am able to produce the following result

    namespace Enums
    {
       public enum IVAC_VT_SHIFT_SPD
       {
          [Description("16.1299991607666")]
          DATA16,
          [Description("32.130001068115234")]
          DATA32,
       }
    }
    
    <Window ...
       xmlns:enums="clr-namespace:StackOverflowAnswers.Wpf.Enums"
       ...>
    
    <ComboBox x:Name="Combo"
       ItemsSource="{Binding Source={enums:EnumBindingSource {x:Type enums:IVAC_VT_SHIFT_SPD}}}"
       DisplayMemberPath="Description"
       SelectedValuePath="Value" />
    

    enter image description here

    To facilitate this, I used an adaptation of the approach outlined in this link: https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/. If you follow that directly, you can achieve pretty similar results without a ViewModel for the enum. However, I like using the ViewModel, since it will allow me to potentially tweak things as needed, like maybe add support for a icon attribute.

    In my example, I created an EnumViewModel class to house what will be displayed in the combobox:

    namespace Enums
    {
        public class EnumViewModel
        {
            public EnumViewModel(Enum value)
            {
                Value = value;
                Description = getDescription(value);
            }
    
            public Enum Value { get; }
            public string Description { get; }
    
            private string getDescription(Enum value)
            {
                if (!(value?.GetType().GetField(value?.ToString()) is FieldInfo enumField))
                    // value is null...
                    return string.Empty;
    
                var descriptionAttribute = enumField.GetCustomAttributes(typeof(DescriptionAttribute), false)
                    .OfType<DescriptionAttribute>()
                    .FirstOrDefault();
    
                if (descriptionAttribute is null || string.IsNullOrEmpty(descriptionAttribute.Description))
                    // description attribute is missing or blank...
                    return value.ToString();
    
                return descriptionAttribute.Description;
            }
        }
    }
    

    And a MarkupExtension to allow me to bind to that directly without having to include it in a view model or code behind. This sets up the ability to use Source={enums:EnumBindingSource ...}.

    namespace Enums
    {
        public class EnumBindingSourceExtension : MarkupExtension
        {
            private Type _enumType;
            public Type EnumType
            {
                get => this._enumType;
                set
                {
                    if (value == this._enumType) return;
    
                    if (!isEnumType(value)) throw new ArgumentException("Type must be for an Enum.");
    
                    this._enumType = value;
                }
            }
    
            private bool isEnumType(Type type)
            {
                if (type is null) return false;
                var enumType = Nullable.GetUnderlyingType(type) ?? type;
                return enumType.IsEnum;
            }
    
            public EnumBindingSourceExtension() { }
    
            public EnumBindingSourceExtension(Type enumType)
            {
                this.EnumType = enumType;
            }
    
            public override object ProvideValue(IServiceProvider serviceProvider)
            {
                if (this._enumType is null)
                    throw new InvalidOperationException("The EnumType must be specified.");
    
                var actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
                var enumValues = Enum.GetValues(actualEnumType)
                    .OfType<Enum>()
                    .Select(x => new EnumViewModel(x))
                    .ToList();
    
                return enumValues;
            }
        }
    }
    

    Now that these are in place, any future Enums can be bound to ComboBoxes as easily as shown at the start.