14

Well the problem is that I have this enum, BUT I don't want the combobox to show the values of the enum. This is the enum:

public enum Mode
    {
        [Description("Display active only")]
        Active,
        [Description("Display selected only")]
        Selected,
        [Description("Display active and selected")]
        ActiveAndSelected
    }

So in the ComboBox instead of displaying Active, Selected or ActiveAndSelected, I want to display the DescriptionProperty for each value of the enum. I do have an extension method called GetDescription() for the enum:

public static string GetDescription(this 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;
            }
        }

So is there a way I can bind the enum to the ComboBox AND show it's content with the GetDescription extension method?

Thanks!

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Carlo
  • 25,602
  • 32
  • 128
  • 176

6 Answers6

20

I would suggest a DataTemplate and a ValueConverter. That will let you customize the way it's displayed, but you would still be able to read the combobox's SelectedItem property and get the actual enum value.

ValueConverters require a lot of boilerplate code, but there's nothing too complicated here. First you create the ValueConverter class:

public class ModeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        return ((Mode) value).GetDescription();
    }
    public object ConvertBack(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Since you're only converting enum values to strings (for display), you don't need ConvertBack -- that's just for two-way binding scenarios.

Then you put an instance of the ValueConverter into your resources, with something like this:

<Window ... xmlns:WpfApplication1="clr-namespace:WpfApplication1">
    <Window.Resources>
        <WpfApplication1:ModeConverter x:Key="modeConverter"/>
    </Window.Resources>
    ....
</Window>

Then you're ready to give the ComboBox a DisplayTemplate that formats its items using the ModeConverter:

<ComboBox Name="comboBox" ...>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource modeConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

To test this, I threw in a Label too, that would show me the actual SelectedItem value, and it did indeed show that SelectedItem is the enum instead of the display text, which is what I would want:

<Label Content="{Binding ElementName=comboBox, Path=SelectedItem}"/>
Joe White
  • 94,807
  • 60
  • 220
  • 330
  • Dude, your answer finally solved my problem after few hours of internet digging. Thanks! – mcy Aug 26 '15 at 13:30
6

This is how I am doing it with MVVM. On my model I would have defined my enum:

    public enum VelocityUnitOfMeasure
    {
        [Description("Miles per Hour")]
        MilesPerHour,
        [Description("Kilometers per Hour")]
        KilometersPerHour
    }

On my ViewModel I expose a property that provides possible selections as string as well as a property to get/set the model's value. This is useful if we don't want to use every enum value in the type:

    //UI Helper
    public IEnumerable<string> VelocityUnitOfMeasureSelections
    {
        get
        {
            var units = new []
                            {
                               VelocityUnitOfMeasure.MilesPerHour.Description(),
                               VelocityUnitOfMeasure.KilometersPerHour.Description()
                            };
            return units;
        }
    }

    //VM property
    public VelocityUnitOfMeasure UnitOfMeasure
    {
        get { return model.UnitOfMeasure; }
        set { model.UnitOfMeasure = value; }
    }

Furthermore, I use a generic EnumDescriptionCoverter:

public class EnumDescriptionConverter : IValueConverter
{
    //From Binding Source
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is Enum)) throw new ArgumentException("Value is not an Enum");
        return (value as Enum).Description();
    }

    //From Binding Target
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is string)) throw new ArgumentException("Value is not a string");
        foreach(var item in Enum.GetValues(targetType))
        {
            var asString = (item as Enum).Description();
            if (asString == (string) value)
            {
                return item;
            }
        }
        throw new ArgumentException("Unable to match string to Enum description");
    }
}

And finally, with the view I can do the following:

<Window.Resources>
    <ValueConverters:EnumDescriptionConverter x:Key="enumDescriptionConverter" />
</Window.Resources>
...
<ComboBox SelectedItem="{Binding UnitOfMeasure, Converter={StaticResource enumDescriptionConverter}}"
          ItemsSource="{Binding VelocityUnitOfMeasureSelections, Mode=OneWay}" />
Mike Rowley
  • 908
  • 9
  • 20
  • is Enum.Description() and extension method? I cannot find that method on type System.Enum .. – mtijn Oct 28 '11 at 10:07
  • .Description() is an extension method that gets the description attribute. In hind sight, it might have been more fitting to use the DisplayName attribute. – Mike Rowley Oct 29 '11 at 15:14
  • I overlooked the extension method in the question body, that was probably what you were referring to, and DisplayName is not used because it does not apply to enum field targets (unless you expand attribute usage) – mtijn Nov 01 '11 at 14:17
  • I like you approach but I'd like to upgrade your convert back method to: `public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return Enum.GetValues(targetType) .OfType() .Single(element => element.Description() == (string)value); }` – memory of a dream Jan 30 '14 at 11:55
6

I like the way you think. But GetCustomAttributes uses reflection. What is that going to do to your performance?

Check out this post: WPF - Displaying enums in ComboBox control http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html

Kamal
  • 2,512
  • 2
  • 34
  • 48
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • 18
    Dude, reflection isn't _that_ slow, especially compared to the time it takes to display a GUI. I wouldn't expect it to be a problem. – Joe White May 27 '09 at 15:40
  • Well, don't take my word for it. The post referenced above says that it is a concern. – Robert Harvey May 27 '09 at 15:58
  • 3
    But doesn't quote any profile results. The author was concerned about it, but that doesn't mean it was actually a problem. – Joe White May 27 '09 at 16:16
  • This is the only one I could get to work. I was able to make it a little shorter using the GetDescription extension method when initializing the Dictionary. Thanks! – Carlo May 27 '09 at 17:21
3

Questions of using reflection and attributes aside, there are a few ways you could do this, but I think the best way is to just create a little view model class that wraps the enumeration value:

public class ModeViewModel : ViewModel
{
    private readonly Mode _mode;

    public ModeViewModel(Mode mode)
    {
        ...
    }

    public Mode Mode
    {
        get { ... }
    }

    public string Description
    {
        get { return _mode.GetDescription(); }
    }
}

Alternatively, you could look into using ObjectDataProvider.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
3

I suggest you use a markup extension I had already posted here, with just a little modification :

[MarkupExtensionReturnType(typeof(IEnumerable))]
public class EnumValuesExtension : MarkupExtension
{
    public EnumValuesExtension()
    {
    }

    public EnumValuesExtension(Type enumType)
    {
        this.EnumType = enumType;
    }

    [ConstructorArgument("enumType")]
    public Type EnumType { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (this.EnumType == null)
            throw new ArgumentException("The enum type is not set");
        return Enum.GetValues(this.EnumType).Select(o => GetDescription(o));
    }
}

You can then use it like that :

<ComboBox ItemsSource="{local:EnumValues local:Mode}"/>

EDIT: the method I suggested will bind to a list of string, which is not desirable since we want the SelectedItem to be of type Mode. It would be better to remove the .Select(...) part, and use a binding with a custom converter in the ItemTemplate.

Community
  • 1
  • 1
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Wouldn't that make the combo box's SelectedItem be "Display active only" instead of Mode.Active? Seems like an undesirable side effect to me. – Joe White May 27 '09 at 15:43
  • So you that means that with this approach I won't be able to set the selected item to what the object with the enum currently has selected? – Carlo May 27 '09 at 15:47
  • @Joe : yes, you're right... that's a problem indeed. I'll update my answer – Thomas Levesque May 27 '09 at 16:04
0

I've done it like this :

<ComboBox x:Name="CurrencyCodeComboBox" Grid.Column="4" DisplayMemberPath="." HorizontalAlignment="Left" Height="22"   Margin="11,6.2,0,10.2" VerticalAlignment="Center" Width="81" Grid.Row="1" SelectedValue="{Binding currencyCode}" >
            <ComboBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel/>
                </ItemsPanelTemplate>
            </ComboBox.ItemsPanel>
        </ComboBox>

in code I set itemSource :

CurrencyCodeComboBox.ItemsSource = [Enum].GetValues(GetType(currencyCode))