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:
September 20, 2008 at 8:28 am |
[...] http://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