How to bind an enum to a combobox control in WPF?
Asked Answered
E

22

218

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.

Egg answered 26/5, 2011 at 22:32 Comment(4)
Here is what you looking for: WPF ObjectDataProvider - Binding Enum to ComboBox You can also download the complete source code example from there.Homocentric
The Best answer in my opinion is in: #59243Gahan
Possible duplicate of Databinding an enum property to a ComboBox in WPFChili
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 youtube.com/watch?v=Bp5LFXjwtQ0Cracking
B
365

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.

Bandur answered 26/5, 2011 at 22:43 Comment(23)
Thanks this seems to be it. Can you please show this in xaml?Egg
@Joan Venge, to bind it in XAML you need to use ObjectDataProvider as described in example given by raj in another answer.Bandur
Thanks but that sample doesn't work. I get this error The property 'MethodParameters' is read-only and cannot be changed.Egg
What about this one? geekswithblogs.net/cskardon/archive/2008/10/16/…Egg
Tested example from first link, works OK. See added code and comment in my answer.Bandur
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?Egg
Found your problem on MSDN forums (social.msdn.microsoft.com/Forums/en/wpf/thread/…). 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.Bandur
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: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…Egg
To the last, try to add Mode=OneWay to ComboBox binding as on the topic from last link.Bandur
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.Egg
Actually the problem is from this line <x:Type TypeName="YourNamespace:EffectStyle"/> I put the Namespace like this: <x:Type TypeName="Motion.VideoEffects:EffectStyle"/> or should I use an alias like you did here? xmlns:YourAssembly="clr-namespace:YourNamespace" So YourAssembly.Egg
Edited code in answer according to the name of your namespace. Should works.Bandur
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?Egg
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.Egg
Your need to add reference to it and add xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName" in XAML to use it. Here is guide: msdn.microsoft.com/en-us/library/ms747086.aspxBandur
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.Egg
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.Bandur
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.Egg
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.Bandur
I see what you mean, I forgot about binding SelectedItem. It works now, thanks my friend.Egg
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 https://mcmap.net/q/82226/-how-to-bind-an-enum-to-a-combobox-control-in-wpfLieabed
How would you do it if the list of enums values sometimes needed to change (e.g. if in some cases one of the values should not be shown) - should you not use enums?Wallraff
@Wallraff enums are a compile-time feature of C#. You should consider a different approach for your case.Bandur
P
144

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; }
}
Perinephrium answered 14/9, 2012 at 18:52 Comment(14)
To make this work, I had to create a ValueDescription class which has public properties for Value and DescriptionSoissons
Yes, you can also change this code to use a Tuple<T1, T2> or or KeyValuePair<TKey, TValue> instead of the ValueDescription class and then you wouldn't have to create your own.Perinephrium
I needed to implement OnPropertyChanged (or the equivalent) for both ViewModel properties, not just SelectedClass.Bradytelic
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.Perinephrium
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.Dodwell
Yep, the binding is using a ValueConverter to convert the property into a list.Perinephrium
I'm trying this with a Tuple<Enum, string> but it keeps throwing errors trying to convert the tuple when selected, despite having the SelectedValuePath set to "Item1".Ethelyn
I apparently was using SelectedItem instead of SelectedValue.Ethelyn
There is a helpful answer further down from @Roger with an alternative for the ValueDescriptionFelicefelicia
This answer & my earlier comment on this answer both already say that you can use other classes instead of a ValueDescription class, such asTuple<T1, T2> or KeyValuePair<TKey, TValue>. However, I decided not to misuse one class just to avoid writing a trivially simple class that's only 5 lines long.Perinephrium
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.Yearly
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.Perinephrium
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.Deceit
@Deceit 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?Perinephrium
F
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

Fortyfour answered 1/7, 2013 at 13:34 Comment(4)
@Perinephrium : 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.Fortyfour
@Fortyfour 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.Perinephrium
@Perinephrium Yes, you are right about that property and backing field :)Fortyfour
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)Acro
C
37

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

Cancellation answered 27/1, 2015 at 14:45 Comment(2)
Perfectly simple solution. Namespace for System as in kirmir's answer: xmlns:System="clr-namespace:System;assembly=mscorlib"Concomitant
Works well in WPF projects of Visual Studio 2017.Middlebreaker
U
15

It works very nice and simple.
xaml

<ComboBox ItemsSource="{Binding MyEnumArray}">

.cs

public Array MyEnumArray
{
  get { return Enum.GetValues(typeof(MyEnum)); }
}
Ultrasonics answered 22/2, 2021 at 13:6 Comment(1)
Very clean solution, can make it more so too public Array MyEnumArray => Enum.GetValues(typeof(MyEnum));Oxazine
N
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.

Ninon answered 30/5, 2013 at 13:58 Comment(1)
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 }Perinephrium
A
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).

Adjudicate answered 26/5, 2011 at 22:37 Comment(1)
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?Egg
C
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

Charissacharisse answered 28/7, 2018 at 19:29 Comment(3)
For me, this is the best solution to the question: simple, easy to understand, straightforward to implement.Denomination
The problem with this solution is that it is unlocalizable.Brynnbrynna
@RobinDavies you can localize it. Requires a custom DescriptionAttribute of which I have built a few. See this SO question for some ideas: #7399153Charissacharisse
L
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.

Lieabed answered 17/9, 2013 at 12:51 Comment(0)
J
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

Jagir answered 26/1, 2016 at 16:48 Comment(0)
F
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

Frustration answered 17/8, 2013 at 20:6 Comment(1)
This answer seems incomplete: * What is /core/?Sensation
T
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.

Terra answered 8/11, 2014 at 16:30 Comment(0)
P
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("_", " ");
    }
}
Protohistory answered 5/8, 2016 at 22:22 Comment(0)
F
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>
Flavoprotein answered 17/7, 2018 at 21:18 Comment(0)
C
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
        }
    }
Curium answered 21/7, 2021 at 12:38 Comment(0)
B
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
Beatrisbeatrisa answered 28/10, 2016 at 22:54 Comment(4)
Kyrylo's answer is much simpler than yours - I don't understand what's complicated about it? His requires zero conversion in code.Marishamariska
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?Beatrisbeatrisa
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!Marishamariska
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.Beatrisbeatrisa
P
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) );
});
Philia answered 12/10, 2017 at 0:22 Comment(1)
I think your ViewModel has an error. 1) Shouldn't it be a ReactiveList of EffectStyleLookup?, 2) You should make an empty ReactiveList<T>() first. Then add the items. Finally: ReactiveList<T> is now deprecated (but still works). EffectStyles = new ReactiveList<EffectStyleLookup>(); EffectStyles.AddRange(list); Thanks for taking the time to show this.Brett
L
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}" />
Loathly answered 22/11, 2018 at 11:40 Comment(0)
O
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.

Onto answered 4/12, 2019 at 10:10 Comment(0)
S
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}">
Saxen answered 5/12, 2019 at 16:34 Comment(0)
F
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!

Feldspar answered 31/7, 2022 at 20:47 Comment(0)
R
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>
Rhett answered 28/7, 2023 at 2:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.