Databinding an enum property to a ComboBox in WPF

Viewed 197838

As an example take the following code:

public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEnum example;

    public ExampleEnum ExampleProperty 
    { get { return example; } { /* set and notify */; } }
}

I want a to databind the property ExampleProperty to a ComboBox, so that it shows the options "FooBar" and "BarFoo" and works in mode TwoWay. Optimally I want my ComboBox definition to look something like this:

<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" />

Currently I have handlers for the ComboBox.SelectionChanged and ExampleClass.PropertyChanged events installed in my Window where I do the binding manually.

Is there a better or some kind of canonical way? Would you usually use Converters and how would you populate the ComboBox with the right values? I don't even want to get started with i18n right now.

Edit

So one question was answered: How do I populate the ComboBox with the right values.

Retrieve Enum values as a list of strings via an ObjectDataProvider from the static Enum.GetValues method:

<Window.Resources>
    <ObjectDataProvider MethodName="GetValues"
        ObjectType="{x:Type sys:Enum}"
        x:Key="ExampleEnumValues">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="ExampleEnum" />
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

This I can use as an ItemsSource for my ComboBox:

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>
15 Answers

I don't know if it is possible in XAML-only but try the following:

Give your ComboBox a name so you can access it in the codebehind: "typesComboBox1"

Now try the following

typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));

you can consider something like that:

  1. define a style for textblock, or any other control you want to use to display your enum:

    <Style x:Key="enumStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="Text" Value="&lt;NULL&gt;"/>
        <Style.Triggers>
            <Trigger Property="Tag">
                <Trigger.Value>
                    <proj:YourEnum>Value1<proj:YourEnum>
                </Trigger.Value>
                <Setter Property="Text" Value="{DynamicResource yourFriendlyValue1}"/>
            </Trigger>
            <!-- add more triggers here to reflect your enum -->
        </Style.Triggers>
    </Style>
    
  2. define your style for ComboBoxItem

    <Style TargetType="{x:Type ComboBoxItem}">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
  3. add a combobox and load it with your enum values:

    <ComboBox SelectedValue="{Binding Path=your property goes here}" SelectedValuePath="Content">
        <ComboBox.Items>
            <ComboBoxItem>
                <proj:YourEnum>Value1</proj:YourEnum>
            </ComboBoxItem>
        </ComboBox.Items>
    </ComboBox>
    

if your enum is large, you can of course do the same in code, sparing a lot of typing. i like that approach, since it makes localization easy - you define all the templates once, and then, you only update your string resource files.

Code

    public enum RULE
    {
        [Description( "Любые, без ограничений" )]
        any,
        [Description( "Любые если будет три в ряд" )]
        anyThree,
        [Description( "Соседние, без ограничений" )]
        nearAny,
        [Description( "Соседние если будет три в ряд" )]
        nearThree
    }

    class ExtendRULE
    {
        public static object Values
        {
            get
            {
                List<object> list = new List<object>();
                foreach( RULE rule in Enum.GetValues( typeof( RULE ) ) )
                {
                    string desc = rule.GetType().GetMember( rule.ToString() )[0].GetCustomAttribute<DescriptionAttribute>().Description;
                    list.Add( new { value = rule, desc = desc } );
                }
                return list;
            }
        }
    }

XAML

<StackPanel>
   <ListBox ItemsSource= "{Binding Source={x:Static model:ExtendRULE.Values}}" DisplayMemberPath="desc" SelectedValuePath="value" SelectedValue="{Binding SelectedRule}"/>
   <ComboBox ItemsSource="{Binding Source={x:Static model:ExtendRULE.Values}}" DisplayMemberPath="desc" SelectedValuePath="value" SelectedValue="{Binding SelectedRule}"/>                        
</StackPanel>

It's a pain to see all to see how certain overly complicated solutions become a "standard (anti-)pattern" for the most trivial problems: the overhead and complexity of implementing a MarkupExtension and especially decorating enum values with attributes should be avoided. Simply implement a data model.

Generally, displaying the enumeration value names to the user is a bad idea. Enumerations are not meant to be displayed in the UI. They are constants that are used in a programmatic context. The value names are not meant for display. They are meant to address the engineer, hence the names usually use special semantics and vocabulary, same as scientific vocabulary is not meant to be understood by the public. Don't hesitate to create a dedicated source for the displayed values.

The problem becomes more evident when localization gets involved.
That's why all posted answers are simply over engeineered. They make a very simple problem look like a critical issue.
It's a fact that the most trivial solution is the best. The subject of the original question is most definitely not an exception.
I highly recommend against any of the provided answers. Although they may work, they add unnecessary complexity to a trivial problem.

Note, that you can always convert an enum to a list of its values or value names by calling the static Enum.GetValues or Enum.GetNames, which both return an IEnumerable that you can directly assign to the ComboBox.ItemsSource property e.g.,via data binding.

IEnumerable<ExampleEnum> values = Enum.GetValues<ExampleEnum>();
IEnumerable<string> names = Enum.GetNames<ExampleEnum>();

Usually, when defining an enumeration, you don't have UI in mind.
Enumeration value names are not chosen based on UI design rules.
Usually, UI labels and text in general are created by people with no developer or programmer background. They usually provide all the required translations to localize the application.
There are many good reasons not to mix UI with the application.
You would never design a class and name its properties with UI (e.g., DataGrid columns) in mind. You may want your column header to contain whitespaces etc.
Same reason why exception messages are directed at developers and not users. You definitely don't want to decorate every property, every exception, enum or whatever data type or member with attributes in order to provide a display name that makes sense to the user in a particular UI context.
You don't want to have UI design bleed into your code base and polute your classes.
Application and its user interface - this are two different problems.
Adding this abstract or virtual extra layer of separation allows e.g., to add enum values that should not be displayed. Or more general, modify code without having to break or modify the UI.

Instead of using attributes and implementing loads of additional logic to extract their values (using reflection), you should use a simple IValueConverter or a dedicated class that provides those display values as a binding source.
Stick to the most common pattern and implement a data model for the ComboBox items, where the class has a property of the enum type as member, that helps you to identify the ComboBox.SelectedItem (in case you need the enum value):

ExampleEnum.cs

// Define enumeration without minding any UI elements and context
public enum ExampleEnum 
{ 
    FooBar = 0, 
    BarFoo 
}

ExampleClass.cs

// Define readable enum display values in the UI context.
// Display names can come from a localizable resource.
public class BindingSource : INotifyPropertyChanged
{
    public BindingSource()
    {
        ItemModels = new List<ItemModel> 
        {
            new ItemModel { Label = "Foo Bar Display", Value = ExampleEnum.FooBar },
            new ItemModel { Label = "Bar Foo Display", Value = ExampleEnum.BarFoo }
        }
    }

    public List<ItemModel> ItemModels { get; }

    private ItemModel selectedItemModel;
    public ItemModel SelectedItemModel { get => selectedItemModel; => set and notify; }
}

ItemModel.cs

public class ItemModel
{   
    public string Label { get; set; }
    public ExampleEnum Value { get; set; }
}

MainWindow.xaml

<Window>
  <Window.DataContext>
    <BindingSource />
  </Window.DataContext>

  <ComboBox ItemsSource="{Binding ItemModels}"
            DisplayMemberName="DisplayValue"
            SelectedItem="{Binding SelectedItemModel}" />
</Window>

Try using

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"
    SelectedValue="{Binding Path=ExampleProperty}" />
Related