views:

5105

answers:

6

The title pretty much stays it all. Say I have an enum with four values:

public enum CompassHeading
{
    North,
    South,
    East,
    West
}

What XAML would be required to have a ComboBox be populated with these items?

<ComboBox ItemsSource="{Binding WhatGoesHere???}" />

Ideally I wouldn't have to set up C# code for this.

Thanks.

+14  A: 

You can use the ObjectDataProvider to do this:

<ObjectDataProvider MethodName="GetValues" 
    ObjectType="{x:Type sys:Enum}" x:Key="odp">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:CompassHeading"/>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ComboBox ItemsSource="{Binding Source={StaticResource odp}}" />

I found the solution here:

http://bea.stollnitz.com/blog/?p=28

casperOne
Thanks, this solution seems to work well with TwoWay binding also. Note that the IsSynchronizedWithCurrentItem="true" is a red herring for this question (you might like to remove it for clarity other visitors).
Drew Noakes
This does not support localization.
Guge
@Guge: No, it doesn't, but enumerations don't support localization either. You would have to create a different enumeration for each locality, or you would have to attach an attribute which could produce the localization values for you (it would use a keyed lookup of some kind), in which case, the question and answer don't apply anymore.
casperOne
@casperOne: Exactly! I don't think of enums as a part of the user interface, just a way of sanitizing the use of integers for symbolic use in code. Making the enums visible to the users in this way smells of poor separation of layers.
Guge
@Guge: Perhaps, but that's more an issue for the person who asked the question, no? The question was very specific, the reasons behind it were not of concern when I answered it.
casperOne
@Guge -- In my case I was building in-house software where everyone spoke English. Localization did not apply.
Drew Noakes
@Drew: Good to see that you agree with me that this solution is not universially applicable.
Guge
+3  A: 

Here is a detailed example of how to bind to enums in WPF

Assume you have the following enum

public enum EmployeeType    
{
    Manager,
    Worker
}

You can then bind in the codebehind

typeComboBox.ItemsSource = Enum.GetValues(typeof(EmployeeType));

or use the ObjectDataProvider

<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="sysEnum">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:EmployeeType" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

and now you can bind in the markup

<ComboBox ItemsSource="{Binding Source={StaticResource sysEnum}}" />

Also check out: http://stackoverflow.com/questions/58743/databinding-an-enum-property-to-a-combobox-in-wpf#62032

rudigrobler
+1  A: 

A third solution:

This is slightly more work up-front, better is easier in the long-run if you're binding to loads of Enums. Use a Converter which takes the enumeration's type as a paramter, and converts it to an array of strings as an output.

In VB.NET:

Public Class EnumToNamesConverter
    Implements IValueConverter

    Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
        Return [Enum].GetNames(DirectCast(value, Type))
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class

Or in C#:

public sealed class EnumToNamesConverter : IValueConverter
{
  object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return Enum.GetNames(value.GetType());
  }

  object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw New NotSupportedException()
  }
}

Then in your Application.xaml, add a global resource to access this converter:

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

Finally use the converter in any XAML pages where you need the values of any Enum...

<ComboBox ItemsSource="{Binding
                        Source={x:Type local:CompassHeading},
                        Converter={StaticResource EnumToNamesConverter}}" />
Mark
Hi maranite2, I like the look of this solution but I couldn't get it to work with TwoWay binding. The binding works from control to data (when I save) but it doesn't work from data to control (the combo box is initially blank where there should be a value selected).
Drew Noakes
+2  A: 

For a step-by-step walkthrough of the alternatives and derivations of technique, try this web page:

The Missing .NET #7: Displaying Enums in WPF

This article demonstrates a method of overriding the presentation of certain values as well. A good read with plenty of code samples.

Drew Noakes
+6  A: 

I think using an ObjectDataProvider to do that is really tedious... I have a more concise suggestion (yes I know, it's a bit late...), using a markup extension :

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

Here is the code for the markup extension :

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

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

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

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (this.EnumType == null)
            throw new ArgumentException("The enum type is not set");
        return Enum.GetValues(this.EnumType);
    }
}
Thomas Levesque
+1  A: 

This may be like swearing in a church, but I'd like to declare each ComboBoxItem explicitly in the XAML for the following reasons:

  • If I need localization I can give the XAML to a translator and keep the code for myself.
  • If I have enum values that aren't suitable for a given ComboBox, I don't have to show them.
  • The order of enums is determined in the XAML, not necessarily in the code.
  • The number of enum values to choose from is usually not very high. I would consider Enums with hundreds of values a code smell.
  • If I need graphics or other ornamentation on some of the ComboBoxItems, it would be easiest to just put it in XAML, where it belongs instead of some tricky Template/Trigger stuff.
  • Keep It Simple, Stupid

C# Sample code:

    public enum number { one, two, three };

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private number _number = number.one;
    public number Number
    {
        get { return _number; }
        set {
            if (_number == value)
                return;
            _number = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Number"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

XAML code:

<Window x:Class="WpfApplication6.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="480" Width="677">
<Grid>
    <ComboBox SelectedValue="{Binding Number}" SelectedValuePath="Tag">
        <ComboBoxItem Content="En" Tag="One"/>
        <ComboBoxItem Content="To" Tag="Two"/>
        <ComboBoxItem Content="Tre" Tag="Three"/>
    </ComboBox>
</Grid>

As you can see, the XAML has been localized to Norwegian, without any need for changes in the C# code.

Guge