47

Is it possible to use the ObjectDataProvider method to bind a ListBox to an enum, and style it somehow to display the Description attriibute? If so how would one go about doing this...?

Michal Ciechan
  • 13,492
  • 11
  • 76
  • 118

6 Answers6

107

Yes, it is possible. This will do it. Say we have the enum

public enum MyEnum
{
    [Description("MyEnum1 Description")]
    MyEnum1,
    [Description("MyEnum2 Description")]
    MyEnum2,
    [Description("MyEnum3 Description")]
    MyEnum3
}

Then we can use the ObjectDataProvider as

xmlns:MyEnumerations="clr-namespace:MyEnumerations"
<ObjectDataProvider MethodName="GetValues"
                ObjectType="{x:Type sys:Enum}"
                x:Key="MyEnumValues">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="MyEnumerations:MyEnum" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

And for the ListBox we set the ItemsSource to MyEnumValues and apply an ItemTemplate with a Converter.

<ListBox Name="c_myListBox" SelectedIndex="0" Margin="8"
        ItemsSource="{Binding Source={StaticResource MyEnumValues}}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

And in the converter we get the description and return it

public class EnumDescriptionConverter : IValueConverter
{
    private string GetEnumDescription(Enum enumObj)
    {
        FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());

        object[] attribArray = fieldInfo.GetCustomAttributes(false);

        if (attribArray.Length == 0)
        {
            return enumObj.ToString();
        }
        else
        {
            DescriptionAttribute attrib = attribArray[0] as DescriptionAttribute;
            return attrib.Description;
        }
    }

    object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Enum myEnum = (Enum)value;
        string description = GetEnumDescription(myEnum);
        return description;
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return string.Empty;
    }
}

The GetEnumDescription method should probably go somewhere else but you get the idea :)

Check GetEnumDescription as extension method.

Stacked
  • 6,892
  • 7
  • 57
  • 73
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • Thanks, will be giving this a try now :) – Michal Ciechan Oct 21 '10 at 13:47
  • 5
    Love it, ganked it. I used a little linq to pair down GetEnumDescription, you can snag it here http://pastebin.com/XLm9hbhG –  Dec 15 '10 at 16:21
  • 2
    So you have to make a converter for each type of enum? – Carlo Jun 27 '11 at 23:30
  • @Carlo: No, in this example the converter is casting value to `MyEnum` but you can just as well use `Enum` if you want a generic converter – Fredrik Hedblad Jun 28 '11 at 15:06
  • +1 from me. Simple and it does the job--just what I needed. BTW, to those who need localization, Sacha Barber has an article over on CodeProject.com that implements a more complex solution that supports localization. – David Veeneman Aug 20 '11 at 01:59
  • 8
    Beware that you have to create an instance of the converter as a ressource, for example: `` – Timm Jul 31 '12 at 13:06
  • 11
    This will break if you have a different attribute on the enum- I'd suggesting changes the code to attrib = attribArray.OfType().FirstOrDefault(); and checking for null instead as it is more robust. – RichardOD Jun 26 '13 at 15:31
  • `GetEnumDescription` should be static – Mr Anderson Oct 17 '19 at 15:04
8

Another solution would be a custom MarkupExtension that generates the items from enum type. This makes the xaml more compact and readable.

using System.ComponentModel;

namespace EnumDemo
{
    public enum Numbers
    {
        [Description("1")]
        One,

        [Description("2")]
        Two,

        Three,
    }
}

Example of usage:

<Window x:Class="EnumDemo.MainWindow"
    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:local="clr-namespace:EnumDemo">

    <ListBox ItemsSource="{local:EnumToCollection EnumType={x:Type local:Numbers}}"/>

</Window>

MarkupExtension implementation

using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Markup;

namespace EnumDemo
{
    public class EnumToCollectionExtension : MarkupExtension
    {
        public Type EnumType { get; set; }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (EnumType == null) throw new ArgumentNullException(nameof(EnumType));

            return Enum.GetValues(EnumType).Cast<Enum>().Select(EnumToDescriptionOrString);
        }

        private string EnumToDescriptionOrString(Enum value)
        {
            return value.GetType().GetField(value.ToString())
                       .GetCustomAttributes(typeof(DescriptionAttribute), false)
                       .Cast<DescriptionAttribute>()
                       .FirstOrDefault()?.Description ?? value.ToString();
        }
    }
}
chviLadislav
  • 1,204
  • 13
  • 15
  • This is way easier than the top answer. I just have a question. When I bind to the property that stores the Enum, it updates properly but does not automatically show the value when I load the window. Do you know how I can fix this? – EatATaco Mar 17 '21 at 16:13
  • @EatATaco are you using a combo box? if so, take a look here: https://stackoverflow.com/q/20479976/5612780 – chviLadislav Mar 18 '21 at 13:10
3

If you bind to the Enum, you could probably convert this to the description through an IValueConverter.

See Binding ComboBoxes to enums... in Silverlight! for a description on how to accomplish this.

See http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx for more information.

Community
  • 1
  • 1
Pieter van Ginkel
  • 29,160
  • 8
  • 71
  • 111
2

You can define a ressource file in your project (*.resx file). In this file you must define "key-value-pairs", something like this:

"YellowCars" : "Yellow Cars",
"RedCars" : "Red Cars",

and so on...

The keys are equals to your enum-entries, something like this:

public enum CarColors
{
    YellowCars,
    RedCars
}

and so on...

When you use WPF you can implement in your XAML-Code, something like this:

<ComboBox ItemsSource="{Binding Source={StaticResource CarColors}}" SelectedValue="{Binding CarColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource CarColorConverter}}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Then you must write your Converter, something like this:

using System;
using System.Globalization;
using System.Resources;
using System.Windows.Data;

public class CarColorConverter : IValueConverter
{
    private static ResourceManager CarColors = new ResourceManager(typeof(Properties.CarColors));

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var key = ((Enum)value).ToString();
        var result = CarColors.GetString(key);
        if (result == null) {
            result = key;
        }

        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

My answer comes 7 years to late ;-) But maybe it can be used by someone else!

peter70
  • 1,053
  • 16
  • 31
0

Yeah, possible.

ListBox can help us do that, without converters.

The steps of this method are below:
create a ListBox and set the ItemsSource for the listbox as the enum and binding the SelectedItem of the ListBox to the selected property.

Then each ListBoxItem will be created.

  • Step 1: define your Enum.
public enum EnumValueNames
{ 
   EnumValueName1, 
   EnumValueName2, 
   EnumValueName3
}

Then add below property to your DataContext (or ViewModel of MVVM), which records the selected item which is checked.

public EnumValueNames SelectedEnumValueName { get; set; }
  • Step 2: add the enum to static resources for your Window, UserControl or Grid etc.
    <Window.Resources>
        <ObjectDataProvider MethodName="GetValues"
                            ObjectType="{x:Type system:Enum}"
                            x:Key="EnumValueNames">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:EnumValueNames" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
  • Step 3: Use the List Box to populate each item
<ListBox ItemsSource="{Binding Source={StaticResource EnumValueNames}}"
              SelectedItem="{Binding SelectedEnumValueName, Mode=TwoWay}" />

References: https://www.codeproject.com/Articles/130137/Binding-TextBlock-ListBox-RadioButtons-to-Enums

Bravo Yeung
  • 8,654
  • 5
  • 38
  • 45
0

The example here is applied to a ComboBox, but will work all the same for any Enum Binding.

Origin:

This anwser is based on the original work of Brian Lagunas' EnumBindingSourceExtension + EnumDescriptionTypeConverter. I have made modifications for it to better suit my needs.

What I changed:

  1. Extended the Enum class with a boolean function that checks if the EnumValue has the [Description] attribute or not

    public static bool HasDescriptionAttribute(this Enum value)
    {
        var attribute = value.GetType().GetField(value.ToString())
                            .GetCustomAttributes(typeof(DescriptionAttribute), false)
                            .FirstOrDefault();                                
    
        return (attribute != null);
    }
    
  2. Modified Brian's "ConvertTo()" function in "EnumDescriptionTypeConverter" to return "null" in case the [Description] attribute was not applied

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        ...
        // Original: return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
        return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : null;
    }
    
  3. Modified Brian's "ProvideValue()" function in "EnumBindingSourceExtension" by editing it's return value by calling my own function

    ProvideValue(IServiceProvider serviceProvider)
    {
        ...
        // Original: return enumValues
        return SortEnumValuesByIndex(enumValues);
    
        ...
        // Original: return tempArray
        return SortEnumValuesByIndex(tempArray);
    }
    

    And adding my function to Sort the enum by Index (Original code had problems when going across Projects ..), and Strip out any Values that don't have the [Description] attribute:

    private object SortEnumValuesByIndex(Array enumValues)
    {
        var values = enumValues.Cast<Enum>().ToList();
        var indexed = new Dictionary<int, Enum>();
        foreach (var value in values)
        {
            int index = (int)Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()));
            indexed.Add(index, value);
        }
    
        return indexed.OrderBy(x => x.Key).Select(x => x.Value).Where(x => x.HasDescriptionAttribute()).Cast<Enum>();
    }     
    

This example has been applied to ComboBoxes:

Note: Failures in Uploading images to the Server, so i added a URL to the images in question

<ComboBox x:Name="ConversionPreset_ComboBox" Grid.Row="4" Grid.Column="1" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:ConversionPreset}}" SelectedIndex="2" SelectionChanged="ConversionPreset_ComboBox_SelectionChanged" />
<ComboBox x:Name="OutputType_ComboBox" Grid.Row="4" Grid.Column="2" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:Output}}" SelectedIndex="1" SelectionChanged="OutputType_ComboBox_SelectionChanged" />

With code behind:

    private Enumeration.Output Output { get; set; }

    private void OutputType_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        => Output = (Enumeration.Output)OutputType_ComboBox.SelectedItem;

    private Enumeration.ConversionPreset ConversionPreset { get; set; }

    private void ConversionPreset_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        => ConversionPreset = (Enumeration.ConversionPreset)ConversionPreset_ComboBox.SelectedItem;

The "ConversionPreset" Enum and a Picture of the ComboBox Rendered

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ConversionPreset
{
    [Description("Very Slow (Smaller File Size)")]
    VerySlow = -2,

    [Description("Slow (Smaller File Size)")]
    Slow = -1,

    [Description("Medium (Balanced File Size)")]
    Medium = 0,

    [Description("Fast (Bigger File Size)")]
    Fast = 1,

    [Description("Very Fast (Bigger File Size)")]
    VeryFast = 2,

    [Description("Ultra Fast (Biggest File Size)")]
    UltraFast = 3
}

The "Output" Enum and a Picture of the ComboBox Rendered

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum Output
{
    // This will be hidden in the Output
    None = -1,

    [Description("Video")]
    Video = 0,

    [Description("Audio")]
    Audio = 1
}