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.
Like this:
Like Loading...
Related
This entry was posted on September 19, 2008 at 1:01 pm and is filed under Uncategorized. You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
September 20, 2008 at 8:28 am |
[…] https://agsmith.wordpress.com/2008/09/19/accessing-enum-members-in-xaml/ […]
September 22, 2008 at 2:26 pm |
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.
September 23, 2008 at 8:10 am |
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.
September 24, 2008 at 10:00 am |
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?
September 24, 2008 at 10:13 am |
Also, can AsString get set in the extension from XAML?
September 24, 2008 at 10:47 am |
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?
September 24, 2008 at 10:47 am |
<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>
September 24, 2008 at 2:02 pm |
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. š
September 24, 2008 at 2:04 pm |
Hmm. The XAML did not post. Here it is:
September 24, 2008 at 2:27 pm |
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.
September 24, 2008 at 2:51 pm |
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);
}
}
September 24, 2008 at 4:01 pm |
It might be more efficient to implement the ConvertBack like Enum.ToObject(targetType, value) so you’re not creating/comparing strings.
September 24, 2008 at 4:18 pm |
yes that is better thanks
February 13, 2009 at 7:04 pm |
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.
May 10, 2009 at 10:37 pm |
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
June 17, 2010 at 4:02 pm |
May I ask which Syntaxhighlighter plugin you use for your wordpress blog here ?
January 27, 2011 at 9:44 am |
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
}
August 17, 2011 at 8:38 am |
Thanks,
It works great ! The best solution found yet !
Eric
August 20, 2011 at 3:12 am |
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.
January 26, 2012 at 6:33 pm |
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
March 16, 2012 at 1:38 pm |
Reblogged this on Harsh Baid and commented:
Nice implementation to bind system built enum like days of week in WPF combo box.
October 22, 2012 at 5:40 pm |
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.
May 11, 2013 at 1:17 am |
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!
September 8, 2013 at 9:21 pm |
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.
October 24, 2016 at 10:42 pm |
It’s amazing in support of me to have a web site, which is beneficial in favor of my experience. thanks admin|
October 13, 2017 at 3:18 pm |
Thanks for finally writing about >Accessing Enum members in Xaml |
Andrew Smith <Loved it!
October 25, 2017 at 3:38 pm |
Hello there I am so glad I found your web site, I really found you by
error, while I was researching on Digg for something else, Regardless I am here now and would just like to say many thanks for a fantastic post and a all round exciting blog
(I also love the theme/design), I don’t have time
to browse it all at the moment but I have saved it
and also included your RSS feeds, so when I have time I will be back to read much more, Please do keep up the superb work.
August 16, 2018 at 6:33 am |
It’s an remarkable piece of writing designed for all the internet users; they will take
advantage from it I am sure.
March 16, 2019 at 9:46 am |
Holdem Reduce and No Control fluctuate because Without Any Restriction, a new player may choice
most of his / her chips whenever you want.