215

I am trying to find a simple example where the enums are shown as is. All examples I have seen tries to add nice looking display strings but I don't want that complexity.

Basically I have a class that holds all the properties that I bind, by first setting the DataContext to this class, and then specifying the binding like this in the xaml file:

<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>

But this doesn't show the enum values in the ComboBox as items.

Andy
  • 30,088
  • 6
  • 78
  • 89
Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • 12
    Here is what you looking for: [WPF ObjectDataProvider - Binding Enum to ComboBox](http://www.codearsenal.net/2012/11/wpf-objectdataprovider-binding-enum-to-combobox.html) You can also download the complete source code example from there. –  Nov 04 '12 at 05:58
  • The Best answer in my opinion is in: http://stackoverflow.com/questions/58743/databinding-an-enum-property-to-a-combobox-in-wpf – gimpy May 21 '15 at 11:47
  • Possible duplicate of [Databinding an enum property to a ComboBox in WPF](https://stackoverflow.com/questions/58743/databinding-an-enum-property-to-a-combobox-in-wpf) – StayOnTarget Apr 01 '19 at 14:57
  • I think a nice way is demonstrated by using a EnumBindingSourceExtension and not by using an ObjectDataProvider, have a look at Brian Lagunas in this youtube https://www.youtube.com/watch?v=Bp5LFXjwtQ0 – Walter Verhoeven May 04 '21 at 08:53

22 Answers22

360

You can do it from code by placing the following code in Window Loaded event handler, for example:

yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast<EffectStyle>();

If you need to bind it in XAML you need to use ObjectDataProvider to create object available as binding source:

<Window x:Class="YourNamespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:StyleAlias="clr-namespace:Motion.VideoEffects">
    <Window.Resources>
        <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                            ObjectType="{x:Type System:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="StyleAlias:EffectStyle"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid>
        <ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
                  SelectedItem="{Binding Path=CurrentEffectStyle}" />
    </Grid>
</Window>

Draw attention on the next code:

xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:StyleAlias="clr-namespace:Motion.VideoEffects"

Guide how to map namespace and assembly you can read on MSDN.

kyrylomyr
  • 12,192
  • 8
  • 52
  • 79
  • Thanks this seems to be it. Can you please show this in xaml? – Joan Venge May 26 '11 at 22:47
  • @Joan Venge, to bind it in XAML you need to use `ObjectDataProvider` as described in example given by **raj** in another answer. – kyrylomyr May 26 '11 at 22:52
  • Thanks but that sample doesn't work. I get this error `The property 'MethodParameters' is read-only and cannot be changed.` – Joan Venge May 26 '11 at 22:53
  • What about this one? http://geekswithblogs.net/cskardon/archive/2008/10/16/databinding-an-enum-in-wpf.aspx – Joan Venge May 26 '11 at 22:53
  • 1
    Tested example from first link, works OK. See added code and comment in my answer. – kyrylomyr May 26 '11 at 23:02
  • Thanks but mine is a usercontrol, not window. Does that matter? Because I still get the same error, and I did replace `xmlns:YourAssembly`, left `system` as is because that's the only one, right? – Joan Venge May 26 '11 at 23:10
  • 1
    Found your problem on MSDN forums (http://social.msdn.microsoft.com/Forums/en/wpf/thread/00f0852a-551b-491c-88fc-bec271674d5c). Try to clean and rebuild project. Probably you should ask for that problem here on another question. This is the only what I can advice... Anyway, the showed example is correct. – kyrylomyr May 26 '11 at 23:15
  • 1
    Thanks, that's bizarre but I have seen similar stuff with wpf madness. Will do and let you know. Btw is this the same problem described here: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/3e81facb-0809-43e3-a8d5-248a22be6e83/ – Joan Venge May 26 '11 at 23:18
  • To the last, try to add `Mode=OneWay` to ComboBox binding as on the topic from last link. – kyrylomyr May 26 '11 at 23:20
  • Now it tells me it cannot find the type EffectStyle even though they are in the same namespace. I am just gonna use the code version. If someone knows they can make it work with the xaml. I don't understand why the xaml version has so many issues, problems. – Joan Venge May 26 '11 at 23:41
  • Actually the problem is from this line `` I put the Namespace like this: `` or should I use an alias like you did here? `xmlns:YourAssembly="clr-namespace:YourNamespace"` So `YourAssembly`. – Joan Venge May 26 '11 at 23:58
  • Edited code in answer according to the name of your namespace. Should works. – kyrylomyr May 27 '11 at 00:10
  • Thanks I now noticed even though the namespaces are the same, the dlls are different. Is it not enough to include the dll that has the enum inside the wpf ui project references? – Joan Venge May 27 '11 at 00:11
  • And I can use this enum in code no problem. It's the xaml that's causing the problems. I exactly used the value namespaces. – Joan Venge May 27 '11 at 00:14
  • 2
    Your need to add reference to it and add `xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"` in XAML to use it. Here is guide: http://msdn.microsoft.com/en-us/library/ms747086.aspx – kyrylomyr May 27 '11 at 00:14
  • Thanks man, now it works. I am really surprised wpf doesn't do these things automatically. Like surely it could have figured out where EffectStyle is coming from, right? I can use it on the code side no problem. – Joan Venge May 27 '11 at 00:24
  • 4
    You can use such tools like ReSharper. It parses all referenced assemblies and gives suggestions what need to include. No need to write - just select from the options. – kyrylomyr May 27 '11 at 00:27
  • Hi again, I was wondering if you would know this too. So the binding now works but it doesn't propagate the changes back to the source. Is it because we are using a temp array that holds the names inside ObjectDataProvider? I could ask a new question but it would take more time to bring everyone to the same page, that's why I wanted to ask here. – Joan Venge May 27 '11 at 21:56
  • 1
    If you need to get selected item then access `SelectedItem` property of `ComboBox` or bind it to some property inside your code. I don't clearly understand what are you awaiting back from binding. Looks like it's already another problem and discussing here will be off-topic. Make another question with detailed information what do you need and what are you going to do with your `ComboBox`. This question is answered. – kyrylomyr May 27 '11 at 23:39
  • 1
    I see what you mean, I forgot about binding SelectedItem. It works now, thanks my friend. – Joan Venge May 27 '11 at 23:57
  • You don't need to explicitly set the ItemSource of your combobox if you are happy to subclass ComboBox. It is possible to ``discover`` the required enumeration just from the binding on ``SelectedValue`` and keep your XAML DRY. See http://stackoverflow.com/a/18850396/158285 – bradgonesurfing Sep 17 '13 at 12:53
141

I like for all objects that I'm binding to be defined in my ViewModel, so I try to avoid using <ObjectDataProvider> in the xaml when possible.

My solution uses no data defined in the View and no code-behind. Only a DataBinding, a reusable ValueConverter, a method to get a collection of descriptions for any Enum type, and a single property in the ViewModel to bind to.

When I want to bind an Enum to a ComboBox the text I want to display never matches the values of the Enum, so I use the [Description()] attribute (from System.ComponentModel) to give it the text that I actually want to see in the ComboBox. If I had an enum of days of the week, it would look something like this:

public enum DayOfWeek
{
  // add an optional blank value for default/no selection
  [Description("")]
  NOT_SET = 0,
  [Description("Sunday")]
  SUNDAY,
  [Description("Monday")]
  MONDAY,
  ...
}

First I created helper class with a couple methods to deal with enums. One method gets a description for a specific value, the other method gets all values and their descriptions for a type.

public static class EnumHelper
{
  public static string Description(this Enum value)
  {
    var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
    if (attributes.Any())
      return (attributes.First() as DescriptionAttribute).Description;

    // If no description is found, the least we can do is replace underscores with spaces
    // You can add your own custom default formatting logic here
    TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
    return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
  }

  public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
  {
    if (!t.IsEnum)
      throw new ArgumentException($"{nameof(t)} must be an enum type");

    return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList();
  }
}

Next, we create a ValueConverter. Inheriting from MarkupExtension makes it easier to use in XAML so we don't have to declare it as a resource.

[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return EnumHelper.GetAllValuesAndDescriptions(value.GetType());
  }
  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return null;
  }
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    return this;
  }
}

My ViewModel only needs 1 property that my View can bind to for both the SelectedValue and ItemsSource of the combobox:

private DayOfWeek dayOfWeek;

public DayOfWeek SelectedDay
{
  get { return dayOfWeek; }
  set
  {
    if (dayOfWeek != value)
    {
      dayOfWeek = value;
      OnPropertyChanged(nameof(SelectedDay));
    }
  }
}

And finally to bind the ComboBox view (using the ValueConverter in the ItemsSource binding)...

<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
          SelectedValuePath="Value"
          DisplayMemberPath="Description"
          SelectedValue="{Binding Path=SelectedDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

To implement this solution you only need to copy my EnumHelper class and EnumToCollectionConverter class. They will work with any enums. Also, I didn't include it here, but the ValueDescription class is just a simple class with 2 public object properties, one called Value, one called Description. You can create that yourself or you can change the code to use a Tuple<object, object> or KeyValuePair<object, object>


For those who wanted to see the ValueDescription class:

public class ValueDescription
{
    public object Value { get; set; }
    public object Description { get; set; }
}
Nick
  • 4,556
  • 3
  • 29
  • 53
  • 9
    To make this work, I had to create a `ValueDescription` class which has public properties for `Value` and `Description` – Perchik May 15 '15 at 19:17
  • 5
    Yes, you can also change this code to use a `Tuple` or or `KeyValuePair` instead of the `ValueDescription` class and then you wouldn't have to create your own. – Nick May 18 '15 at 13:16
  • I needed to implement OnPropertyChanged (or the equivalent) for both ViewModel properties, not just SelectedClass. – Will Apr 19 '16 at 20:32
  • 2
    You shouldn't need to implement OnPropertyChanged for the property that returns the list. The list is generated from the values in an Enum. It will never change during run time, and when it never changes, it never needs to notify anyone that it has changed. Also, with the updated version, the list property isn't even needed at all. – Nick Apr 19 '16 at 20:34
  • How is the combobox's ItemSource and SelectedValue the same property. Doesn't the ItemsSource need to be a list? Oh, I see, it's bcuase the EnumHelper makes a list of objects. this actually makes my ViewModel simpler since I don't have to maintain a separate list of objects to populate the ItemSource. – Stealth Rabbi Mar 07 '17 at 18:51
  • 1
    Yep, the binding is using a ValueConverter to convert the property into a list. – Nick Mar 07 '17 at 22:04
  • I'm trying this with a Tuple but it keeps throwing errors trying to convert the tuple when selected, despite having the SelectedValuePath set to "Item1". – Chris Jul 25 '19 at 20:11
  • I apparently was using `SelectedItem` instead of `SelectedValue`. – Chris Jul 25 '19 at 20:18
  • There is a helpful answer further down from @Roger with an alternative for the `ValueDescription` – Coden Dec 26 '19 at 20:45
  • This answer & my earlier comment on this answer both already say that you can use other classes instead of a `ValueDescription` class, such as`Tuple` or `KeyValuePair`. However, I decided not to misuse one class just to avoid writing a trivially simple class that's only 5 lines long. – Nick Jan 03 '20 at 20:49
  • 1
    i like your answer and sample, although it is incomplete. sometimes i really don't understand people providing good solutions and in the same time making things more complicated than needed. Why didn't you just add the ValueDescription class? also the OnPropertyChanged Method is missing. anyway, thanks for your answer. – Christian Casutt Jul 01 '20 at 07:11
  • 5
    I really don't understand people that have complex problems they need to solve and can't even figure out how to write a class with nothing but 2 `object` properties without hand holding. – Nick Jul 23 '20 at 14:48
  • 1
    That's a little unfair. It's not immediately obvious what `ValueDescription` is intended to be. Beginners may have struggled through far enough to understand that they need to bind an enum to a combobox but not really understand how your answer works. You provide so much in your answer that it _looks_ like something they can use with a simple copy-paste and minimal understanding, but it won't work. Suddenly they have no idea why it doesn't work and it's not clear how to fix it. I do feel it's a bit of an odd decision to make people figure out the entire code just to fill in the last 5% IMHO. – Clonkex Nov 19 '21 at 04:36
  • @Clonkex literally from the answer: "the ValueDescription class is just a simple class with 2 public object properties, one called Value, one called Description." how is it not obvious? – Nick Dec 04 '21 at 04:12
64

I used another solution using MarkupExtension.

  1. I made class which provides items source:

    public class EnumToItemsSource : MarkupExtension
    {
        private readonly Type _type;
    
        public EnumToItemsSource(Type type)
        {
            _type = type;
        }
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return Enum.GetValues(_type)
                .Cast<object>()
                .Select(e => new { Value = (int)e, DisplayName = e.ToString() });
        }
    }
    
  2. That's almost all... Now use it in XAML:

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
    
  3. Change 'enums:States' to your enum

Greg Gum
  • 33,478
  • 39
  • 162
  • 233
tom.maruska
  • 1,411
  • 16
  • 22
  • 1
    @Nick : Accepted answer is referencing enum (or model as you said) in xaml too. Your solution is creating 2 properties and backing field in view model, which I didn't like (DRY rule). And of course, you don't have to use `e.ToString()` for display name. You can use your own translator, descrtiption attribute parser, whatever. – tom.maruska Sep 13 '15 at 09:03
  • 2
    @tom.maruska I'm not trying to get into my answer vs your answer, but since you brought it up, having 2 properties does not violate DRY rule when they are 2 distinct properties that serve different purposes. And your answer would also require adding a property (you even called out this property yourself `{Binding Path=WhereEverYouWant}`) and if you want it to support 2-way binding you're going to have a backing field for it, too. So you're not replacing 2 properties and 1 backing field by doing this, you're only replacing 1 single-line readonly property. – Nick Sep 14 '15 at 17:16
  • @Nick Yes, you are right about that property and backing field :) – tom.maruska Sep 14 '15 at 17:31
  • That worked like a charm! Although I had to remove cast `(int)e` - it seems unnecessary and also produces binding warnings (and red border around combobox) – Amomum Dec 15 '22 at 10:46
35

Use ObjectDataProvider:

<ObjectDataProvider x:Key="enumValues"
   MethodName="GetValues" ObjectType="{x:Type System:Enum}">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="local:ExampleEnum"/>
      </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>

and then bind to static resource:

ItemsSource="{Binding Source={StaticResource enumValues}}"

based on this article

druss
  • 1,820
  • 19
  • 18
13

Nick's answer has really helped me, but I realised it could be tweaked slightly, to avoid an extra class, ValueDescription. I remembered that there exists a KeyValuePair class already in the framework, so this can be used instead.

The code changes only slightly :

public static IEnumerable<KeyValuePair<string, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("TEnum must be an Enumeration type");
        }

        return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>()
               select new KeyValuePair<string, string>(e.ToString(),  e.Description());
    }


public IEnumerable<KeyValuePair<string, string>> PlayerClassList
{
   get
   {
       return EnumHelper.GetAllValuesAndDescriptions<PlayerClass>();
   }
}

and finally the XAML :

<ComboBox ItemSource="{Binding Path=PlayerClassList}"
          DisplayMemberPath="Value"
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=SelectedClass}" />

I hope this is helpful to others.

Gangula
  • 5,193
  • 4
  • 30
  • 59
Roger
  • 131
  • 1
  • 2
  • My first implementation did use a `KeyValuePair` but in the end I decided using a `KeyValuePair` to represent something that is not a key-value pair just to avoid writing a trivially simple class didn't make a ton of sense. The `ValueDescription` class is only 5 lines, and 2 of them are just `{` and `}` – Nick Jan 03 '20 at 20:46
8

You'll need to create an array of the values in the enum, which can be created by calling System.Enum.GetValues(), passing it the Type of the enum that you want the items of.

If you specify this for the ItemsSource property, then it should be populated with all of the enum's values. You probably want to bind SelectedItem to EffectStyle (assuming it is a property of the same enum, and contains the current value).

Andy
  • 30,088
  • 6
  • 78
  • 89
  • Thanks, can you show the first part in code please? I am not sure where to store the enum values as array? The enum property is located in another class. Can I do this GetValues step inside xaml? – Joan Venge May 26 '11 at 22:46
7

There are many excellent answers to this question and I humbly submit mine. I find that mine is somewhat simpler and more elegant. It requires only a value converter.

Given an enum...

public enum ImageFormat
{
    [Description("Windows Bitmap")]
    BMP,
    [Description("Graphics Interchange Format")]
    GIF,
    [Description("Joint Photographic Experts Group Format")]
    JPG,
    [Description("Portable Network Graphics Format")]
    PNG,
    [Description("Tagged Image Format")]
    TIFF,
    [Description("Windows Media Photo Format")]
    WDP
}

and a value converter...

public class ImageFormatValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is ImageFormat format)
        {
            return GetString(format);
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is string s)
        {
            return Enum.Parse(typeof(ImageFormat), s.Substring(0, s.IndexOf(':')));
        }
        return null;
    }

    public string[] Strings => GetStrings();

    public static string GetString(ImageFormat format)
    {
        return format.ToString() + ": " + GetDescription(format);
    }

    public static string GetDescription(ImageFormat format)
    {
        return format.GetType().GetMember(format.ToString())[0].GetCustomAttribute<DescriptionAttribute>().Description;

    }
    public static string[] GetStrings()
    {
        List<string> list = new List<string>();
        foreach (ImageFormat format in Enum.GetValues(typeof(ImageFormat)))
        {
            list.Add(GetString(format));
        }

        return list.ToArray();
    }
}

resources...

    <local:ImageFormatValueConverter x:Key="ImageFormatValueConverter"/>

XAML declaration...

    <ComboBox Grid.Row="9" ItemsSource="{Binding Source={StaticResource ImageFormatValueConverter}, Path=Strings}"
              SelectedItem="{Binding Format, Converter={StaticResource ImageFormatValueConverter}}"/>

View model...

    private ImageFormat _imageFormat = ImageFormat.JPG;
    public ImageFormat Format
    {
        get => _imageFormat;
        set
        {
            if (_imageFormat != value)
            {
                _imageFormat = value;
                OnPropertyChanged();
            }
        }
    }

Resulting combobox...

ComboBox bound to enum

AQuirky
  • 4,691
  • 2
  • 32
  • 51
  • For me, this is the best solution to the question: simple, easy to understand, straightforward to implement. – Informagic Jan 17 '19 at 20:34
  • The problem with this solution is that it is unlocalizable. – Robin Davies Jun 19 '20 at 11:25
  • @RobinDavies you can localize it. Requires a custom DescriptionAttribute of which I have built a few. See this SO question for some ideas: https://stackoverflow.com/questions/7398653/how-to-localize-a-property-description-in-c – AQuirky Jul 02 '20 at 22:20
7

It works very nice and simple.
xaml

<ComboBox ItemsSource="{Binding MyEnumArray}">

.cs

public Array MyEnumArray
{
  get { return Enum.GetValues(typeof(MyEnum)); }
}
Minwoo Yu
  • 360
  • 2
  • 13
  • Very clean solution, can make it more so too `public Array MyEnumArray => Enum.GetValues(typeof(MyEnum));` – LiamJM Nov 23 '22 at 11:10
4

All the above posts have missed a simple trick. It is possible from the binding of SelectedValue to find out how to populate the ItemsSource AUTOMAGICALLY so that your XAML markup is just.

<Controls:EnumComboBox SelectedValue="{Binding Fool}"/>

For example in my ViewModel I have

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


    FoolEnum _Fool;
    public FoolEnum Fool
    {
        get { return _Fool; }
        set { ValidateRaiseAndSetIfChanged(ref _Fool, value); }
    }

ValidateRaiseAndSetIfChanged is my INPC hook. Yours may differ.

The implementation of EnumComboBox is as follows but first I'll need a little helper to get my enumeration strings and values

    public static List<Tuple<object, string, int>> EnumToList(Type t)
    {
        return Enum
            .GetValues(t)
            .Cast<object>()
            .Select(x=>Tuple.Create(x, x.ToString(), (int)x))
            .ToList();
    }

and the main class ( Note I'm using ReactiveUI for hooking property changes via WhenAny )

using ReactiveUI;
using ReactiveUI.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Documents;

namespace My.Controls
{
    public class EnumComboBox : System.Windows.Controls.ComboBox
    {
        static EnumComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumComboBox), new FrameworkPropertyMetadata(typeof(EnumComboBox)));
        }

        protected override void OnInitialized( EventArgs e )
        {
            base.OnInitialized(e);

            this.WhenAnyValue(p => p.SelectedValue)
                .Where(p => p != null)
                .Select(o => o.GetType())
                .Where(t => t.IsEnum)
                .DistinctUntilChanged()
                .ObserveOn(RxApp.MainThreadScheduler)
                .Subscribe(FillItems);
        }

        private void FillItems(Type enumType)
        {
            List<KeyValuePair<object, string>> values = new List<KeyValuePair<object,string>>();

            foreach (var idx in EnumUtils.EnumToList(enumType))
            {
                values.Add(new KeyValuePair<object, string>(idx.Item1, idx.Item2));
            }

            this.ItemsSource = values.Select(o=>o.Key.ToString()).ToList();

            UpdateLayout();
            this.ItemsSource = values;
            this.DisplayMemberPath = "Value";
            this.SelectedValuePath = "Key";

        }
    }
}

You also need to set the style correctly in Generic.XAML or your box won't render anything and you will pull your hair out.

<Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
</Style>

and that is that. This could obviously be extended to support i18n but would make the post longer.

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
3

Universal apps seem to work a bit differently; it doesn't have all the power of full-featured XAML. What worked for me is:

  1. I created a list of the enum values as the enums (not converted to strings or to integers) and bound the ComboBox ItemsSource to that
  2. Then I could bind the ComboBox ItemSelected to my public property whose type is the enum in question

Just for fun I whipped up a little templated class to help with this and published it to the MSDN Samples pages. The extra bits let me optionally override the names of the enums and to let me hide some of the enums. My code looks an awful like like Nick's (above), which I wish I had seen earlier.

Running the sample; it includes multiple twoway bindings to the enum

PESMITH_MSFT
  • 350
  • 1
  • 9
2

If you are binding to an actual enum property on your ViewModel, not a int representation of an enum, things get tricky. I found it is necessary to bind to the string representation, NOT the int value as is expected in all of the above examples.

You can tell if this is the case by binding a simple textbox to the property you want to bind to on your ViewModel. If it shows text, bind to the string. If it shows a number, bind to the value. Note I have used Display twice which would normally be an error, but it's the only way it works.

<ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}"
                      DisplayMemberPath="Display"
                      SelectedValuePath="Display"
                      ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" />

Greg

Greg Gum
  • 33,478
  • 39
  • 162
  • 233
2
public class EnumItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!value.GetType().IsEnum)
            return false;

        var enumName = value.GetType();
        var obj = Enum.Parse(enumName, value.ToString());

        return System.Convert.ToInt32(obj);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Enum.ToObject(targetType, System.Convert.ToInt32(value));
    }
}

You should extend Rogers and Greg's answer with such kind of Enum value converter, if you're binding straight to enum object model properties.

Ruberoid
  • 1,585
  • 1
  • 9
  • 12
1

I liked tom.maruska's answer, but I needed to support any enum type which my template might encounter at runtime. For that, I had to use a binding to specify the type to the markup extension. I was able to work in this answer from nicolay.anykienko to come up with a very flexible markup extension which would work in any case I can think of. It is consumed like this:

<ComboBox SelectedValue="{Binding MyEnumProperty}" 
          SelectedValuePath="Value"
          ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}" 
          DisplayMemberPath="DisplayName" />

The source for the mashed up markup extension referenced above:

class EnumToObjectArray : MarkupExtension
{
    public BindingBase SourceEnum { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        DependencyObject targetObject;
        DependencyProperty targetProperty;

        if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty)
        {
            targetObject = (DependencyObject)target.TargetObject;
            targetProperty = (DependencyProperty)target.TargetProperty;
        }
        else
        {
            return this;
        }

        BindingOperations.SetBinding(targetObject, EnumToObjectArray.SourceEnumBindingSinkProperty, SourceEnum);

        var type = targetObject.GetValue(SourceEnumBindingSinkProperty).GetType();

        if (type.BaseType != typeof(System.Enum)) return this;

        return Enum.GetValues(type)
            .Cast<Enum>()
            .Select(e => new { Value=e, Name = e.ToString(), DisplayName = Description(e) });
    }

    private static DependencyProperty SourceEnumBindingSinkProperty = DependencyProperty.RegisterAttached("SourceEnumBindingSink", typeof(Enum)
                       , typeof(EnumToObjectArray), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));

    /// <summary>
    /// Extension method which returns the string specified in the Description attribute, if any.  Oherwise, name is returned.
    /// </summary>
    /// <param name="value">The enum value.</param>
    /// <returns></returns>
    public static string Description(Enum value)
    {
        var attrs = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attrs.Any())
            return (attrs.First() as DescriptionAttribute).Description;

        //Fallback
        return value.ToString().Replace("_", " ");
    }
}
Community
  • 1
  • 1
Hamish
  • 183
  • 2
  • 12
1

Simple and clear explanation: http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/

xmlns:local="clr-namespace:BindingEnums"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

...

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

...

<Grid>
    <ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"
              ItemsSource="{Binding Source={StaticResource dataFromEnum}}"/>
</Grid>
jlo-gmail
  • 4,453
  • 3
  • 37
  • 64
1
<Window.Resources>
        <ObjectDataProvider x:Key="DiaryTypeEnum"
       MethodName="GetValues" ObjectType="{x:Type System:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="z:Enums+DiaryType"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
</Window.Resources>
...
<ComboBox ItemsSource="{Binding Source={StaticResource DiaryTypeEnum}}" SelectedItem="{x:Static z:Enums+DiaryType.Defect}" />

Where z its xmlns:z="clr-namespace:ProjName.Helpers"

My Enum into static class

  public static class Enums
    {
        public enum DiaryType
        {
            State,
            Defect,
            Service,
            Other
        }
        public enum OtherEnumOrMethods
        {
           //TODO
        }
    }
z zz
  • 31
  • 2
0

I'm adding my comment (in VB, sadly, but the concept can be easily replicated over to C# in a heartbeat), because I just had to reference this and didn't like any of the answers as they were too complex. It shouldn't have to be this difficult.

So I came up with an easier way. Bind the Enumerators to a Dictionary. Bind that dictionary to the Combobox.

My combobox:

<ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2" 
    Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104" 
    SelectedValuePath="Key" DisplayMemberPath="Value" />

My code-behind. Hopefully, this helps someone else out.

Dim tDict As New Dictionary(Of Integer, String)
Dim types = [Enum].GetValues(GetType(Helper.Enumerators.AllowedType))
For Each x As Helper.Enumerators.AllowedType In types
    Dim z = x.ToString()
    Dim y = CInt(x)
    tDict.Add(y, z)
Next

cmbRole.ClearValue(ItemsControl.ItemsSourceProperty)
cmbRole.ItemsSource = tDict
Eray Balkanli
  • 7,752
  • 11
  • 48
  • 82
Laki Politis
  • 147
  • 9
  • Kyrylo's answer is much simpler than yours - I don't understand what's complicated about it? His requires zero conversion in code. – Johnathon Sullinger Nov 17 '16 at 22:46
  • I didn't want to place all my logic into the hands of XAML. I prefer to do my logic my way (not always the best way), but it allows me to understand where and why something isn't going according to plan. His is less complicated, but relies on XAML/WPF to do the logic. I'm just not a fan of that. 10,000 ways to skin a cat, you know? – Laki Politis Nov 23 '16 at 16:44
  • Fair enough. I personally prefer to use features already built, out of the box, for me but that's just my preference ;) To each there own! – Johnathon Sullinger Nov 23 '16 at 19:37
  • Yessir! I completely understand. I've been forced into Software development coming from web development. I haven't been as up-to-date on WPF and had to learn alot as I've gone along. I still don't understand all the intricacies of the WPF/XAML controls, and so I've been finding more hiccups than solutions in how I would expect things to work. But I appreciate this conversation. It's made me do some more research. – Laki Politis Nov 24 '16 at 03:50
0

Using ReactiveUI, I've created the following alternate solution. It's not an elegant all-in-one solution, but I think at the very least it's readable.

In my case, binding a list of enum to a control is a rare case, so I don't need to scale the solution across the code base. However, the code can be made more generic by changing EffectStyleLookup.Item into an Object. I tested it with my code, no other modifications are necessary. Which means the one helper class could be applied to any enum list. Though that would reduce its readability - ReactiveList<EnumLookupHelper> doesn't have a great ring to it.

Using the following helper class:

public class EffectStyleLookup
{
    public EffectStyle Item { get; set; }
    public string Display { get; set; }
}

In the ViewModel, convert the list of enums and expose it as a property:

public ViewModel : ReactiveObject
{
  private ReactiveList<EffectStyleLookup> _effectStyles;
  public ReactiveList<EffectStyleLookup> EffectStyles
  {
    get { return _effectStyles; }
    set { this.RaiseAndSetIfChanged(ref _effectStyles, value); }
  }

  // See below for more on this
  private EffectStyle _selectedEffectStyle;
  public EffectStyle SelectedEffectStyle
  {
    get { return _selectedEffectStyle; }
    set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); }
  }

  public ViewModel() 
  {
    // Convert a list of enums into a ReactiveList
    var list = (IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle))
      .Select( x => new EffectStyleLookup() { 
        Item = x, 
        Display = x.ToString()
      });

    EffectStyles = new ReactiveList<EffectStyle>( list );
  }
}

In the ComboBox, utilise the SelectedValuePath property, to bind to the original enum value:

<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />

In the View, this allows us to bind the original enum to the SelectedEffectStyle in the ViewModel, but display the ToString() value in the ComboBox:

this.WhenActivated( d =>
{
  d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) );
  d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) );
});
Mitkins
  • 4,031
  • 3
  • 40
  • 77
  • I think your ViewModel has an error. 1) Shouldn't it be a ReactiveList of EffectStyleLookup?, 2) You should make an empty ReactiveList() first. Then add the items. Finally: ReactiveList is now deprecated (but still works). EffectStyles = new ReactiveList(); EffectStyles.AddRange(list); Thanks for taking the time to show this. – user1040323 Feb 26 '19 at 11:57
0

Nick's solution can be simplified more, with nothing fancy, you would only need a single converter:

[ValueConversion(typeof(Enum), typeof(IEnumerable<Enum>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var r = Enum.GetValues(value.GetType());
        return r;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

You then use this wherever you want your combo box to appear:

<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}"  SelectedItem="{Binding PagePosition}" />
Gangula
  • 5,193
  • 4
  • 30
  • 59
Jack
  • 29
  • 4
0

I wouldn't recommend implementing this as it is but hopefully this can inspire a good solution.

Let's say your enum is Foo. Then you can do something like this.

public class FooViewModel : ViewModel
{
    private int _fooValue;

    public int FooValue
    {
        get => _fooValue;
        set
        {
            _fooValue = value;
            OnPropertyChange();
            OnPropertyChange(nameof(Foo));
            OnPropertyChange(nameof(FooName));
        }
    }
    public Foo Foo 
    { 
        get => (Foo)FooValue; 
        set 
        { 
            _fooValue = (int)value;
            OnPropertyChange();
            OnPropertyChange(nameof(FooValue));
            OnPropertyChange(nameof(FooName));
        } 
    }
    public string FooName { get => Enum.GetName(typeof(Foo), Foo); }

    public FooViewModel(Foo foo)
    {
        Foo = foo;
    }
}

Then on Window.Load method you can load all enums to an ObservableCollection<FooViewModel> which you can set as the DataContext of the combobox.

Shaamil Ahmed
  • 378
  • 2
  • 7
0

I just kept it simple. I created a list of items with the enum values in my ViewModel:

public enum InputsOutputsBoth
{
    Inputs,
    Outputs,
    Both
}

private IList<InputsOutputsBoth> _ioTypes = new List<InputsOutputsBoth>() 
{ 
    InputsOutputsBoth.Both, 
    InputsOutputsBoth.Inputs, 
    InputsOutputsBoth.Outputs 
};

public IEnumerable<InputsOutputsBoth> IoTypes
{
    get { return _ioTypes; }
    set { }
}

private InputsOutputsBoth _selectedIoType;

public InputsOutputsBoth SelectedIoType
{
    get { return _selectedIoType; }
    set
    {
        _selectedIoType = value;
        OnPropertyChanged("SelectedIoType");
        OnSelectionChanged();
    }
}

In my xaml code I just need this:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
Tsjakka
  • 71
  • 7
0

here is my short answer.

public enum Direction { Left, Right, Up, Down };
public class Program
{
    public Direction ScrollingDirection { get; set; }
    public List<string> Directions { get; } = new List<string>();

    public Program()
    {
        loadListDirection();
    }

    private void loadListDirection()
    {
        Directions.AddRange(Enum.GetNames(typeof(Direction)));
    }
}

And Xaml:

<ComboBox SelectedIndex="0" ItemsSource="{Binding Path=Directions, Mode=OneWay}" SelectedItem="{Binding Path=ScrollingDirection, Mode=TwoWay}"/>

Good Luck!

Piotras
  • 1
  • 1
0

Let me post another option.

Let's say we want a ComboBox populated with the CalendarSelectionMode enum.

This class will hold the items in the target enum.

public class EnumOptions<T> : ReadOnlyCollection<T> where T : Enum
{
    public EnumOptions() : this(Enum.GetValues(typeof(T)).Cast<T>().ToImmutableArray())
    {
    }

    private EnumOptions(IList<T> list) : base(list)
    {
    }
}

We need this class to use EnumOptions<T> in XAML.

public class CalendarSelectionModeOptions : EnumOptions<CalendarSelectionMode>
{
}

This class is optional.

public partial class EnumCamelCaseToSpacedStringConverter : IValueConverter
{
    public bool OnlyTheFirstWordFirstCharacterIsUpperCase { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo cultureInfo)
    {
        if (value?.ToString() is not string stringValue)
        {
            throw new ArgumentException("Value must be a string", nameof(value));
        }

        if (stringValue.Length is 0)
        {
            return string.Empty;
        }

        string spacedString = CamelCaseToSpacedRegex().Replace(stringValue, " $1");

        if (OnlyTheFirstWordFirstCharacterIsUpperCase is false)
        {
            return spacedString;
        }

        string onlyTheFirstCharacterUpperCase = $"{spacedString[0].ToString()?.ToUpper()}{spacedString[1..].ToLower()}";
        return onlyTheFirstCharacterUpperCase;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultureInfo) => throw new NotImplementedException();


    [GeneratedRegex("(\\B[A-Z])")]
    private static partial Regex CamelCaseToSpacedRegex();
}

And we use it like this:

<Window.Resources>
    <local:CalendarSelectionModeOptions x:Key="CalendarSelectionModeOptions" />
    <local:EnumCamelCaseToSpacedStringConverter
        x:Key="CamelCaseStringConverter"
        OnlyTheFirstWordFirstCharacterIsUpperCase="True" />
</Window.Resources>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <ComboBox
        x:Name="CalendarSelectionModeComboBox"
        Grid.Row="0"
        ItemsSource="{StaticResource CalendarSelectionModeOptions}"
        SelectedIndex="0">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Converter={StaticResource CamelCaseStringConverter}}" />
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>
    <Calendar
        Grid.Row="1"
        SelectionMode="{Binding ElementName=CalendarSelectionModeComboBox, Path=SelectedValue}" />
</Grid>
Andrew KeepCoding
  • 7,040
  • 2
  • 14
  • 21