So, the web, and StackOverflow, have plenty of nice answers for how to bind a combobox to an enum property in WPF. But Silverlight is missing all of the features that make this
I find that a simple encapsulation of enum data is way easier to use.
public ReadOnly property MonsterGroupRole as list(of string)
get
return [Enum].GetNames(GetType(GroupRoleEnum)).Tolist
End get
End Property
private _monsterEnum as GroupRoleEnum
Public Property MonsterGroupRoleValue as Integer
get
return _monsterEnum
End get
set(value as integer)
_monsterEnum=value
End set
End Property
...
<ComboBox x:Name="MonsterGroupRole"
ItemsSource="{Binding MonsterGroupRole,
Mode=OneTime}"
SelectedIndex="{Binding MonsterGroupRoleValue ,
Mode=TwoWay}" />
And this will completly remove the need of a converter... :)
There is another way to bind ComboBox to enums without the need of a custom converter for the selected item. You can check it at
http://charlass.wordpress.com/2009/07/29/binding-enums-to-a-combobbox-in-silverlight/
It doesn't use the DescriptionAttributes.... but it works perfectly for me, so i guess it depends on the scenario it will be used
Here is the same setup for a Windows 8.1/Windows Phone universal App, main changes are:-
It seems that the order of the XAML is important too, I had to put ItemsSource before SelectedIndex otherwise it didn't call the ItemsSource binding e.g.
<ComboBox
ItemsSource="{Binding Path=MyProperty,Mode=OneWay, Converter={StaticResource EnumToIEnumerableConverter}}"
SelectedIndex="{Binding Path=MyProperty, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}"
/>
Code below
namespace MyApp.Converters
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Windows.UI.Xaml.Data;
public class EnumToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// Note: as pointed out by Martin in the comments on this answer, this line
// depends on the enum values being sequentially ordered from 0 onward,
// since combobox indices are done that way. A more general solution would
// probably look up where in the GetValues array our value variable
// appears, then return that index.
return (int) value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
public class EnumToIEnumerableConverter : IValueConverter
{
private readonly Dictionary<TypeInfo, List<object>> _cache = new Dictionary<TypeInfo, List<object>>();
public object Convert(object value, Type targetType, object parameter, string language)
{
var type = value.GetType().GetTypeInfo();
if (!_cache.ContainsKey(type))
{
var fields = type.DeclaredFields.Where(field => field.IsLiteral);
var values = new List<object>();
foreach (var field in fields)
{
var a = (DescriptionAttribute[]) field.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (a != null && a.Length > 0)
{
values.Add(a[0].Description);
}
else
{
values.Add(field.GetValue(value));
}
}
_cache[type] = values;
}
return _cache[type];
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
[AttributeUsage(AttributeTargets.Field)]
public class DescriptionAttribute : Attribute
{
public string Description { get; private set; }
public DescriptionAttribute(string description)
{
Description = description;
}
}
}
Agh, I spoke too soon! There is a perfectly good solution, at least in Silverlight 3. (It might only be in 3, since this thread indicates that a bug related to this stuff was fixed in Silverlight 3.)
Basically, you need a single converter for the ItemsSource
property, but it can be entirely generic without using any of the prohibited methods, as long as you pass it the name of a property whose type is MyEnum
. And databinding to SelectedItem
is entirely painless; no converter needed! Well, at least it is as long as you don't want custom strings for each enum value via e.g. the DescriptionAttribute
, hmm... will probably need another converter for that one; hope I can make it generic.
Update: I made a converter and it works! I have to bind to SelectedIndex
now, sadly, but it's OK. Use these guys:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
namespace DomenicDenicola.Wpf
{
public class EnumToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Note: as pointed out by Martin in the comments on this answer, this line
// depends on the enum values being sequentially ordered from 0 onward,
// since combobox indices are done that way. A more general solution would
// probably look up where in the GetValues array our value variable
// appears, then return that index.
return (int)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Enum.Parse(targetType, value.ToString(), true);
}
}
public class EnumToIEnumerableConverter : IValueConverter
{
private Dictionary<Type, List<object>> cache = new Dictionary<Type, List<object>>();
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var type = value.GetType();
if (!this.cache.ContainsKey(type))
{
var fields = type.GetFields().Where(field => field.IsLiteral);
var values = new List<object>();
foreach (var field in fields)
{
DescriptionAttribute[] a = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (a != null && a.Length > 0)
{
values.Add(a[0].Description);
}
else
{
values.Add(field.GetValue(value));
}
}
this.cache[type] = values;
}
return this.cache[type];
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
With this sort of binding XAML:
<ComboBox x:Name="MonsterGroupRole"
ItemsSource="{Binding MonsterGroupRole,
Mode=OneTime,
Converter={StaticResource EnumToIEnumerableConverter}}"
SelectedIndex="{Binding MonsterGroupRole,
Mode=TwoWay,
Converter={StaticResource EnumToIntConverter}}" />
And this sort of resource-declaration XAML:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ddwpf="clr-namespace:DomenicDenicola.Wpf">
<Application.Resources>
<ddwpf:EnumToIEnumerableConverter x:Key="EnumToIEnumerableConverter" />
<ddwpf:EnumToIntConverter x:Key="EnumToIntConverter" />
</Application.Resources>
</Application>
Any comments would be appreciated, as I'm somewhat of a XAML/Silverlight/WPF/etc. newbie. For example, will the EnumToIntConverter.ConvertBack
be slow, so that I should consider using a cache?