Here's my enum:
enum Foo
{
[Description("the quick")]
Bar,
[Description("brown fox")]
Baz,
[Description("jumped over")]
Qux
}
Here's part of my ViewModel:
class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<RowViewModel> Rows { get { ... } }
}
class RowViewModel : ViewModelBase
{
public String Name { get { ... } set { ... } }
public Foo Foo { get { ... } set { ... } }
}
Here's my XAML:
<DataGrid AutoGeneratingColumn="OnAutoGeneratingColumn" ItemsSource="{Binding Path=Rows}" />
Because MainWindowViewModel.Rows[n].Foo
is an enum, WPF will automatically generate a DataGridComboBoxColumn
with the enum members as combobox drop-down values, so far so good.
I want the combobox to use the [Description("")]
values in the combobox drop-down and display. So I implemented an IValueConverter
. I then added a handler for OnAutoGeneratingColumn
:
private void OnAutoGeneratingColumn(Object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGridComboBoxColumn dgCol = e.Column as DataGridComboBoxColumn;
if( dgCol != null && e.PropertyType.IsEnum ) {
Binding binding = new Binding( e.PropertyName );
binding.Converter = EnumDescriptionValueConverter.Instance;
dgCol.TextBinding = binding;
dgCol.SelectedItemBinding = binding;
}
}
Unfortunately this doesn't work: the cells appear empty when they have values and the combobox drop-down contains the original enum values rather than descriptions.
You are working too hard to do this, since the enum is not a changeable item, during the VM construction enumerate the enum and extract the descriptions into a list then bind the control to that list.
Example
Take our enums
public enum TheNums
{
[Description("One")]
Alpha,
[Description("Two")]
Beta,
[Description("Three")]
Gamma
}
The extension method to extract descriptions
public static class AttributeExtension
{
/// <summary>If an attribute on an enumeration exists, this will return that
/// information</summary>
/// <param name="value">The object which has the attribute.</param>
/// <returns>The description string of the attribute or string.empty</returns>
public static string GetAttributeDescription(this object value)
{
string retVal = string.Empty;
try
{
retVal = value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.OfType<DescriptionAttribute>()
.First()
.Description;
}
catch (NullReferenceException)
{
//Occurs when we attempt to get description of an enum value that does not exist
}
finally
{
if (string.IsNullOrEmpty(retVal))
retVal = "Unknown";
}
return retVal;
}
}
The creation of our list of strings List<string> EnumDescriptions { get; set; }
:
EnumDescriptions = new List<string>()
{
TheNums.Alpha.GetAttributeDescription(),
TheNums.Beta.GetAttributeDescription(),
TheNums.Gamma.GetAttributeDescription()
};
For the sake of simplicity I will add it to the page's datacontext so I don't have to path into the list named EnumDescriptions
.
public List<string> EnumDescriptions { get; set; }
public MainWindow()
{
InitializeComponent();
EnumDescriptions = new List<string>()
{
TheNums.Alpha.GetAttributeDescription(),
TheNums.Beta.GetAttributeDescription(),
TheNums.Gamma.GetAttributeDescription()
};
DataContext = EnumDescriptions;
}
Then on my page I will bind to it directly, since Listbox inheirits the page's datacontext which is the EnumDescriptions
.
<ListBox ItemsSource="{Binding}" Width="100" Height="200"/>
The result is:
Note that in an MVVM implementation, most likely the whole VM instance would be the page's data context, so the binding needs to know the property name (its binding path
) off of the data context/ the VM instance, so use Binding EnumDescriptions
or Binding Path=EnumDescriptions
.