26

I have a class:

public class AccountDetail
{
    public DetailScope Scope
    {
        get { return scope; }
        set { scope = value; }
    }

    public string Value
    {
        get { return this.value; }
        set { this.value = value; }
    }

    private DetailScope scope;
    private string value;

    public AccountDetail(DetailScope scope, string value)
    {
        this.scope = scope;
        this.value = value;
    }
}

and an enum:

public enum DetailScope
{
    Private, 
    Business, 
    OtherDetail
}

Lastly, I have a .xaml file:

<Window x:Class="Gui.Wpf.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Test" 
    SizeToContent="WidthAndHeight">

    <Grid>
        <ComboBox 
            Name="ScopeComboBox" 
            Width="120" 
            Height="23" 
            Margin="12" />
    </Grid>
</Window>

I would like to do two things:

  1. I wish to data bind DetailsScope enum values to the combo box values. I don't wish to bind enum values directly because the last enum value would be OtherDetail instead of Other detail (added a space character and small letter 'd').
  2. I wish to data bind the selected value in the combo box to the one specified in the instance of the AccountDetail object.

Could you help me out? Thanks.

Update: I found this post http://blogs.msdn.com/b/wpfsdk/archive/2007/02/22/displaying-enum-values-using-data-binding.aspx. I need something similar.

Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
Boris
  • 9,986
  • 34
  • 110
  • 147

4 Answers4

44

A pretty easy way to do this is to use an ObjectDataProvider

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

Use the ObjectDataProvider as the ItemsSource for the ComboBox, bind SelectedItem to the Scope property and apply a converter for the display of each ComboBoxItem

<ComboBox Name="ScopeComboBox"
          ItemsSource="{Binding Source={StaticResource DetailScopeDataProvider}}"
          SelectedItem="{Binding Scope}"
          Width="120"
          Height="23"
          Margin="12">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource CamelCaseConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

And in the converter you can use Regex for CamelCase string splitter found in this question. I used the most advanced version but you can probably use a simplier one. OtherDetail + the regex = Other Detail. Making return value lower and then return a string with first Character UpperCase should give you expected result

public class CamelCaseConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string enumString = value.ToString();
        string camelCaseString = Regex.Replace(enumString, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ").ToLower();
        return char.ToUpper(camelCaseString[0]) + camelCaseString.Substring(1);
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }
}
Community
  • 1
  • 1
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
15

The way I have always done it is as follows. The beauty of this solution is that it is completely generic, and can be re-used for any enumeration types.

1) When you define an enumeration make use of some custom attributes to give a little information. In this example I've used Browsable(false) to indicate that this enumerator is internal, and I don't want to see this option in a combo box. Description("") allows me to specify a Display name for the enumeration.

public enum MyEnumerationTypeEnum
  {
    [Browsable(false)]
    Undefined,
    [Description("Item 1")]
    Item1,
    [Description("Item 2")]
    Item2,
    Item3
  }

2) Define a class which I've called EnumerationManager. This is a generic class that analyzes an Enumeration type and generates a list of values. If an enumerator has Browsable set to false it will be skipped. If it has a Description attribute then it will use the description string as the display name. If no description is found it will just show the default string of the enumerator.

public class EnumerationManager
  {
    public static Array GetValues(Type enumeration)
    {
      Array wArray = Enum.GetValues(enumeration);
      ArrayList wFinalArray = new ArrayList();
      foreach(Enum wValue in wArray)
      {
        FieldInfo fi = enumeration.GetField(wValue.ToString());
        if(null != fi)
        {
          BrowsableAttribute[] wBrowsableAttributes = fi.GetCustomAttributes(typeof(BrowsableAttribute),true) as BrowsableAttribute[];
          if(wBrowsableAttributes.Length > 0)
          {
            //  If the Browsable attribute is false
            if(wBrowsableAttributes[0].Browsable == false)
            {
              // Do not add the enumeration to the list.
              continue;
            }        
          }

          DescriptionAttribute[] wDescriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute),true) as DescriptionAttribute[];
      if(wDescriptions.Length > 0)
      {
        wFinalArray.Add(wDescriptions[0].Description);
      }
      else 
        wFinalArray.Add(wValue);
        }
      }

      return wFinalArray.ToArray();
    }
  }

3) In your xaml add a section in your ResourceDictionary

  <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type l:EnumerationManager}" x:Key="OutputListForMyComboBox">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="l:MyEnumerationTypeEnum" />
      </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>

4) Now just bind the ItemsSource of your combobox to this key we just defined in the resource dictionary

<ComboBox Name="comboBox2" 
          ItemsSource="{Binding Source={StaticResource OutputListForMyComboBox}}" />

If you try this code using my enumeration above you should see 3 items in your combo box:

Item 1
Item 2
Item3

Hope this helps.

EDITS: If you add the implementation of the LocalizableDescriptionAttribute and use that instead the Description attribute that I was using that would be perfect.

Liz
  • 8,780
  • 2
  • 36
  • 40
  • This is an excellent method. – gakera Jun 08 '15 at 16:43
  • What is the best way to bind the SelectedItem back to the viewmodel? I'm trying to bind directly to an enum of the same type in the viewmodel, but then it gets the description as a string sent from the control and parsing it (especially if localized) is a pain? – gakera Nov 24 '15 at 13:05
  • Finally found solution, Thank You. – SkyDancer Dec 30 '21 at 23:10
2

Here is a solution : you create a property (a list) which contains all the possibilities and you bind your ComboBox on that property.

In the XAML :

<ComboBox
    Name="ScopeComboBox" 
    Width="120" 
    Height="23" 
    Margin="12" 
    ItemsSource="{Binding Path=AccountDetailsProperty}"
    DisplayMemberPath="Value"/>

And in the code behind :

public partial class Window1 : Window
{
    public Window1() 
    {
        AccountDetailsProperty = new List<AccountDetail>()
        {
            new AccountDetail(DetailScope.Business, "Business"),
            new AccountDetail(DetailScope.OtherDetail, "Other details"),
            new AccountDetail(DetailScope.Private, "Private"),
        };

        InitializeComponent();
        this.DataContext = this;
    }

    public List<AccountDetail> AccountDetailsProperty { get; set; }
}
Nicolas
  • 6,289
  • 4
  • 36
  • 51
  • Nicolas, thanks for the reply. I am looking a solution which is more XAML oriented, something like: http://blogs.msdn.com/b/wpfsdk/archive/2007/02/22/displaying-enum-values-using-data-binding.aspx – Boris Nov 29 '10 at 18:49
1

I would use a Value Converter for this, this will allow you to bind directly using the converter, you can change the Convert implementation to make for a "nicer" human readable presentation of the enums, i.e. split on capitalized characters.

There's a full article about this approach here.

  public class MyEnumToStringConverter : IValueConverter
  {
     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
         return value.ToString();
     }

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
         return (MyEnum) Enum.Parse( typeof ( MyEnum ), value.ToString(), true );
     }
  }

BrokenGlass
  • 158,293
  • 28
  • 286
  • 335