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!

6 Answers 6

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}"/>
Sign up to request clarification or add additional context in comments.

1 Comment

Dude, your answer finally solved my problem after few hours of internet digging. Thanks!
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

4 Comments

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.
Well, don't take my word for it. The post referenced above says that it is a concern.
But doesn't quote any profile results. The author was concerned about it, but that doesn't mean it was actually a problem.
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!
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}" />

4 Comments

is Enum.Description() and extension method? I cannot find that method on type System.Enum ..
.Description() is an extension method that gets the description attribute. In hind sight, it might have been more fitting to use the DisplayName attribute.
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)
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<Enum>() .Single(element => element.Description() == (string)value); }
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.

3 Comments

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.
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?
@Joe : yes, you're right... that's a problem indeed. I'll update my answer
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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.