It's a pain to see all to see how certain overly complicated solutions become a "standard (anti-)pattern" for the most trivial problems: the overhead and complexity of implementing a MarkupExtension
and especially decorating enum values with attributes should be avoided. Simply implement a data model.
Generally, displaying the enumeration value names to the user is a bad idea. Enumerations are not meant to be displayed in the UI. They are constants that are used in a programmatic context. The value names are not meant for display. They are meant to address the engineer, hence the names usually use special semantics and vocabulary, same as scientific vocabulary is not meant to be understood by the public. Don't hesitate to create a dedicated source for the displayed values.
The problem becomes more evident when localization gets involved.
That's why all posted answers are simply over engeineered. They make a very simple problem look like a critical issue.
It's a fact that the most trivial solution is the best. The subject of the original question is most definitely not an exception.
I highly recommend against any of the provided answers. Although they may work, they add unnecessary complexity to a trivial problem.
Note, that you can always convert an enum to a list of its values or value names by calling the static Enum.GetValues
or Enum.GetNames
, which both return an IEnumerable
that you can directly assign to the ComboBox.ItemsSource
property e.g.,via data binding.
IEnumerable<ExampleEnum> values = Enum.GetValues<ExampleEnum>();
IEnumerable<string> names = Enum.GetNames<ExampleEnum>();
Usually, when defining an enumeration, you don't have UI in mind.
Enumeration value names are not chosen based on UI design rules.
Usually, UI labels and text in general are created by people with no developer or programmer background. They usually provide all the required translations to localize the application.
There are many good reasons not to mix UI with the application.
You would never design a class and name its properties with UI (e.g., DataGrid
columns) in mind. You may want your column header to contain whitespaces etc.
Same reason why exception messages are directed at developers and not users. You definitely don't want to decorate every property, every exception, enum or whatever data type or member with attributes in order to provide a display name that makes sense to the user in a particular UI context.
You don't want to have UI design bleed into your code base and polute your classes.
Application and its user interface - this are two different problems.
Adding this abstract or virtual extra layer of separation allows e.g., to add enum values that should not be displayed. Or more general, modify code without having to break or modify the UI.
Instead of using attributes and implementing loads of additional logic to extract their values (using reflection), you should use a simple IValueConverter
or a dedicated class that provides those display values as a binding source.
Stick to the most common pattern and implement a data model for the ComboBox
items, where the class has a property of the enum type as member, that helps you to identify the ComboBox.SelectedItem
(in case you need the enum value):
ExampleEnum.cs
// Define enumeration without minding any UI elements and context
public enum ExampleEnum
{
FooBar = 0,
BarFoo
}
ExampleClass.cs
// Define readable enum display values in the UI context.
// Display names can come from a localizable resource.
public class BindingSource : INotifyPropertyChanged
{
public BindingSource()
{
ItemModels = new List<ItemModel>
{
new ItemModel { Label = "Foo Bar Display", Value = ExampleEnum.FooBar },
new ItemModel { Label = "Bar Foo Display", Value = ExampleEnum.BarFoo }
}
}
public List<ItemModel> ItemModels { get; }
private ItemModel selectedItemModel;
public ItemModel SelectedItemModel { get => selectedItemModel; => set and notify; }
}
ItemModel.cs
public class ItemModel
{
public string Label { get; set; }
public ExampleEnum Value { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<BindingSource />
</Window.DataContext>
<ComboBox ItemsSource="{Binding ItemModels}"
DisplayMemberName="Label"
SelectedItem="{Binding SelectedItemModel}" />
</Window>