Accessing Enum members in Xaml

Sacha posted a nice article on how to get friendly names for enums. I’ve seen this kind of approach before using the Description attribute. One of the things that he does though is create a static method to obtain the list of enum members. You can however actually get the list with pure xaml as follows:

<ObjectDataProvider MethodName=”GetValues”
               ObjectType=”{x:Type sys:Enum}”
               x:Key=”DayOfWeekValues”>
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName=”sys:DayOfWeek” />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

And then you can wire it up to a combobox:

<ComboBox x:Name=”cboDayOfWeek” 
  ItemsSource=”{Binding Source={StaticResource DayOfWeekValues}}” />

The only problem though is that its verbose (as well as a little heavy for something that is just meant to return a static list). When I write a test app for a control I’m writing I tend to end up having a lot of these and it ends up cluttering the xaml. I was thinking about it and an alternative approach which combines the above as well as takes Sacha’s issue into account would be to write a markup extension that combines all this together.

    /// <summary>
    /// Markup extension that provides a list of the members of a given enum.
    /// </summary>
    public class EnumListExtension : MarkupExtension
    {
        #region Member Variables

 

        private Type _enumType;
        private bool _asString;

 

        #endregion //Member Variables

 

        #region Constructor
        /// <summary>
        /// Initializes a new <see cref=”EnumListExtension”/>
        /// </summary>
        public EnumListExtension()
        {
        }

 

        /// <summary>
        /// Initializes a new <see cref=”EnumListExtension”/>
        /// </summary>
        /// <param name=”enumType”>The type of enum whose members are to be returned.</param>
        public EnumListExtension(Type enumType)
        {
            this.EnumType = enumType;
        }
        #endregion //Constructor

 

        #region Properties
        /// <summary>
        /// Gets/sets the type of enumeration to return
        /// </summary>
        public Type EnumType
        {
            get { return this._enumType; }
            set
            {
                if (value != this._enumType)
                {
                    if (null != value)
                    {
                        Type enumType = Nullable.GetUnderlyingType(value) ?? value;

 

                        if (enumType.IsEnum == false)
                            throw new ArgumentException(“Type must be for an Enum.”);
                    }

 

                    this._enumType = value;
                }
            }
        }

 

        /// <summary>
        /// Gets/sets a value indicating whether to display the enumeration members as strings using the Description on the member if available.
        /// </summary>
        public bool AsString
        {
            get { return this._asString; }
            set { this._asString = value; }
        }
        #endregion //Properties

 

        #region Base class overrides
        /// <summary>
        /// Returns a list of items for the specified <see cref=”EnumType”/>. Depending on the <see cref=”AsString”/> property, the
        /// items will be returned as the enum member value or as strings.
        /// </summary>
        /// <param name=”serviceProvider”>An object that provides services for the markup extension.</param>
        /// <returns></returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (null == this._enumType)
                throw new InvalidOperationException(“The EnumType must be specified.”);

 

            Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
            Array enumValues = Enum.GetValues(actualEnumType);

 

            // if the object itself is to be returned then just use GetValues
            //
            if (this._asString == false)
            {
                if (actualEnumType == this._enumType)
                    return enumValues;

 

                Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
                enumValues.CopyTo(tempArray, 1);
                return tempArray;
            }

 

            List<string> items = new List<string>();

 

            if (actualEnumType != this._enumType)
                items.Add(null);

 

            // otherwise we must process the list
            foreach (object item in Enum.GetValues(this._enumType))
            {
                string itemString = item.ToString();
                FieldInfo field = this._enumType.GetField(itemString);
                object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), false);

 

                if (null != attribs && attribs.Length > 0)
                    itemString = ((DescriptionAttribute)attribs[0]).Description;

 

                items.Add(itemString);
            }

 

            return items.ToArray();
        }
        #endregion //Base class overrides
    }

The markup extension has a Type property that can be used to indicate the enum type for which it should obtain the list. If you want it to return strings as opposed to the actual enum values and take the Description attribute into account, you just set the AsString property to true. So now the hookup is much simpler:

<ComboBox x:Name=”cboDayOfWeek”
  ItemsSource=”{Binding Source={local:EnumList {x:Type sys:DayOfWeek}}}” />

 

Note: I updated this code to use the DisplayName instead of the DescriptionAttribute after Mike Brown correctly pointed out that DisplayName is a more appropriate attribute for this scenario.

Updated: I reverted back to using the Description attribute since the DisplayNameAttribute’s usage does not allow its use on Fields. Thanks to John for pointing this out.

About these ads

24 Responses to “Accessing Enum members in Xaml”

  1. sachabarber.net » Binding And Using Localized Enums In WPF Says:

    [...] http://agsmith.wordpress.com/2008/09/19/accessing-enum-members-in-xaml/ [...]

  2. John Says:

    Correct me if I’m wrong, but I think Mike Brown is wrong in the assumption to use DisplayName in this case. The DescriptionAttribute can be applied to any target, but DisplayNameAttribute is restricted to Class, Method, Property or Event types. It cannot be used on enum items to expand on a prettier display value.

    This puts you back to using DescriptionAttribute or just creating a custom Attribute.

  3. agsmith Says:

    John, you’re absolutely right. I should have tried it out before I updated the article but I was a little pressed for time. Thanks for pointing this out. I’ve updated the article to revert back to the Description attribute.

  4. Ron Says:

    Thanks for this. Would you could you add to it to show how to bind ComboBox SelectedIndex to an actual value in a data source using the extension technique?

  5. Ron Says:

    Also, can AsString get set in the extension from XAML?

  6. agsmith Says:

    Ron, yes AsString can get set in the xaml. The syntax would be something like: {local:EnumList {x:Type sys:DayOfWeek}, AsString=true} or {local:EnumList EnumType={x:Type sys:DayOfWeek}, AsString=true}.

    With regards to your previous question of binding, I’m not sure what specifically you were having trouble with. As you seem to have alluded to, if you’re using the AsString you would have to use bind to the SelectedIndex and couldn’t rely on the SelectedValue/SelectedItem as those would just be a string (and one that could have been localized) so you have to be careful that if you ever modify the enumeration that you just add to the end of it. If you wanted to use the Description attribute and use the actual enum value then you may be better off having the extension return an array of custom object and have a custom datatemplate for that. When I get a chance I’ll put something like that together. For now, is this what you needed to see regarding binding the SelectedIndex?

  7. agsmith Says:

    <DockPanel>
        <DockPanel.Resources>
            <XmlDataProvider x:Key="ctrls" XPath="Controls/Control">
                <x:XData>
                    <Controls xmlns="">
                        <Control Name="Combo" Dock="1" />
                        <Control Name="Label" Dock="3" />
                    </Controls>
                </x:XData>
            </XmlDataProvider>
        </DockPanel.Resources>
        <StackPanel DataContext="{StaticResource ctrls}">
            <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding XPath=@Name}" />
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <ComboBox ItemsSource="{igSamples:EnumList {x:Type Dock}}" 
                      SelectedIndex="{Binding XPath=@Dock}" />
            <TextBox Text="{Binding XPath=@Name}" />
        </StackPanel>
    </DockPanel>

  8. Ron Says:

    Wow Thanks. That is pure XAML and it works with no binding errors. The data source I am using is in C#. Specifically the following code leads to binding errors using selected index:

    ——–
    using System.ComponentModel;
    using System.Windows;
    namespace EnumFun
    {
    public enum DrinkType
    {
    [Description("le Cafe")] Coffee = 0,
    [Description("le The")] Tea,
    [Description("Burbon")] Burbon
    };
    public partial class Window1 : Window
    {
    public class Refreshment
    {
    public DrinkType Beverage { get; set; }
    }
    public Window1()
    {
    this.DataContext = new Refreshment() {Beverage=DrinkType.Burbon };
    InitializeComponent();
    }
    }
    }
    Errors are:
    - Cannot create default converter to perform ‘two-way’ conversions between types ‘EnumFun.DrinkType’ and ‘System.Int32′. Consider using Converter property of Binding. BindingExpression:Path=Beverage; DataItem=’Refreshment’ (HashCode=52727599); target element is ‘ComboBox’ (Name=”); target property is ‘SelectedIndex’ (type ‘Int32′)
    - Value produced by BindingExpression is not valid for target property.; Value=’Burbon’ BindingExpression:Path=Beverage; DataItem=’Refreshment’ (HashCode=52727599); target element is ‘ComboBox’ (Name=”); target property is ‘SelectedIndex’ (type ‘Int32′)

    Don’t know why but WPF makes me want Bourbon first. :)

  9. Ron Says:

    Hmm. The XAML did not post. Here it is:

  10. agsmith Says:

    The xaml didn’t post. Anyway, the binding error you’re getting is pretty clear. Since you’re property type is of the enum type and the selectedindex is numeric, you need a converter on the binding. I haven’t tried it but you might be able to handle this by defining your own typeconverter on the enum or the property.

  11. Ron Says:

    Yes. That worked. Thanks.
    public class EnumToIntConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    return (int) value;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    return Enum.Parse(targetType, value.ToString(), false);
    }
    }

  12. agsmith Says:

    It might be more efficient to implement the ConvertBack like Enum.ToObject(targetType, value) so you’re not creating/comparing strings.

  13. Ron Says:

    yes that is better thanks

  14. Jason Says:

    This seems to work OK, except I get an error – No constructor fro type ‘EnumListExtension’ has 1 parameter. Compiles and runs fine.

    If I use EnumType={x:Type local:MyEnum} (instead of the constructor version) then it doesn’t even compile and run. I get the error: Unknown property ‘EnumType’ for type ‘MS.Internal.Markup.MarkupExtensionParser+UnknownMarkupExtension’ encountered while parsing a Markup Extension. Line 86 Position 27.

    Any ideas? Has anyone else encountered this problem?

    I’m using VS 2008 Pro SP1.

  15. Parag Shravagi Says:

    I am facing a strange problem.
    I want to define an enumeration with a description attribute attached to every member of enum.
    I want to bind the enum with a comboBox in such a way that the combo box displays the attribute values instead of original enum members.
    I would like to define a generic template/solution as I have multiple combo boxes which each of which must be bound to seperate enums.
    Can anybody please help me in writing this?
    Thanks in advance

  16. Michael Says:

    May I ask which Syntaxhighlighter plugin you use for your wordpress blog here ?

  17. Daniel Turan Says:

    Alternative approach asString is custom EnumConverter. The convenience is that combobox’s itemssource are enum values (not strings), but displayed as defined in DescriptionAttribute.

    sample:
    [TypeConverter(typeof(CustomEnumConverter))]
    public enum MyLovelyEnum
    {
    [Description("Member of value 5")]
    SomeValue = 5,

    [Description("Member of value 9")]
    AnotherValue = 9
    }

    and the CustomEnumConverter:
    public class CustomEnumConverter : EnumConverter
    {
    public CustomEnumConverter(Type type)
    : base(type)
    {
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
    if (destinationType == typeof(string))
    {
    return EnumToFriendlyNameConverter.Instance.Convert(value, destinationType, null, culture);;
    }
    return base.ConvertTo(context, culture, value, destinationType);
    }
    }

    And the EnumToFriendlyNameConverter:

    [ValueConversion(typeof(object), typeof(String))]
    public class EnumToFriendlyNameConverter : IValueConverter
    {
    public static EnumToFriendlyNameConverter Instance
    {
    get
    {
    if (_Instance == null) _Instance = new EnumToFriendlyNameConverter();
    return _Instance;
    }
    }
    private static EnumToFriendlyNameConverter _Instance;

    #region IValueConverter implementation

    ///
    /// Convert value for binding from source object
    ///
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
    // To get around the stupid WPF designer bug
    if (value != null)
    {
    FieldInfo fi = value.GetType().GetField(value.ToString());

    // To get around the stupid WPF designer bug
    if (fi != null)
    {
    //works with LocalizedDescriptionAttribute as well
    var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

    return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description)))
    ? attributes[0].Description : value.ToString();
    }
    }

    return string.Empty;
    }

    ///
    /// ConvertBack value from binding back to source object
    ///
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
    throw new Exception(“Cant convert back”);
    }
    #endregion
    }

  18. Eric Ouellet Says:

    Thanks,

    It works great ! The best solution found yet !

    Eric

  19. vallarasus Says:

    Wouldn’t this be the right way to do this?

    foreach (object item in Enum.GetValues(this._enumType))
    {
    Type type = TypeDescriptor.GetReflectioype(item);

    while (type.HasElementType)
    {
    type = type.GetElementType();
    }

    TypeConverter converter = TypeDescriptor.GetConverter(type);

    if (converter != null)
    items.Add((string)converter.ConvertTo(item, typeof(string)));
    else
    items.Add(Convert.ToString(item, CultureInfo.CurrentCulture));
    }

    Vallarasu S.

  20. Lonely Coder Says:

    Thanks….

    For binding to the displayable enum in the combobox, you could create a wrapper class that stores the enum in it, and override it’s ToString, with something like an Enum.Displayable() extension method…. then instead of adding strings to the list, you would add the new wrapper object… ;)
    Works for me…
    cheers

  21. harsh.baid Says:

    Reblogged this on Harsh Baid and commented:
    Nice implementation to bind system built enum like days of week in WPF combo box.

  22. Domingo Says:

    What’s up to all, how is all, I think every one is getting more from this web site, and your views are fastidious for new users.

  23. discount furniture Says:

    Howdy would you mind stating which blog platform you’re using? I’m looking to start my
    own blog soon but I’m having a difficult time deciding between BlogEngine/Wordpress/B2evolution and Drupal. The reason I ask is because your design and style seems different then most blogs and I’m looking for something completely unique.

    P.S Apologies for getting off-topic but I
    had to ask!

  24. http://www.homeizea.com/living-room-ideas-2013/ Says:

    Excellent goods from you, man. I’ve understand
    your stuff previous to and you’re just extremely wonderful.
    I actually like what you’ve acquired here, really like what you’re saying and the way in
    which you say it. You make it enjoyable and you still care for
    to keep it sensible. I can’t wait to read much more from you.
    This is actually a great site.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: