Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Displaying User Friendly Enum Values in WPF

0.00/5 (No votes)
12 Mar 2014 4  
Presents a helper class to easily display user-friendly enum value representations including customizable and localizable text, images, and arbitrary XAML content. This class can also be used as a general XAML switch statement for enum and non-enum classes.

EnumItemList/EnumItemList.GIF

Introduction

Enums provide a convenient way for programmers to restrict and describe values allowed for a field or parameter, but how do we expose them to the user? A common scenario is to display the values in a list or combobox, but just exposing the code names directly is often not appropriate and localizable.

This article presents a small but powerful helper class to make it easier to show the allowed values from enumerations in user-friendly and localizable ways, including showing not only text but arbitrary graphical WPF content.

Background

A commonly recommended way to enlist enum values is to use the ObjectDataProvider to invoke the static Enum.GetValues method:

<ObjectDataProvider MethodName="GetValues"
                    ObjectType="{x:Type sys:Enum}"
                    x:Key="FontStylesValues">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="e:FontStyles" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ComboBox  ItemsSource="{Binding Source={StaticResource FontStylesValues}}" />

The advantage with this method is that it does not require any code-behind. The disadvantages are that it is verbose and it is not possible to customize or localize the texts. Sorting the items in alphabetical order using only XAML seems to be impossible.

In contrast to the solutions presented on The Code Project before by David Veeneman, Tom F Wright and Sacha Barber, the solution presented below does not rely on binding value converters (IValueConverter). Instead the enum values and its corresponding visual representation are stored in a collection of type EnumItemList that is automatically populated. Enum representations can be localized strings, bitmaps or icons from resource files and even arbitrary XAML content. As EnumItemList also can be used for non-enum types, this makes it generally useful as a XAML switch statement.

Using the code

Basic usage

Let us start with the simplest use case which is to just show all the enum values as named by the programmer without any customization:

<ComboBox SelectedValue="{Binding FontStyle}" SelectedValuePath="Value" >
  <ComboBox.ItemsSource>
    <e:EnumItemList EnumType=”{x:Type e:FontStyles}” />
  </ComboBox.ItemsSource> 
</ComboBox>

The EnumItemList class represents a collection of enumeration value-name pairs that are populated by specifying the EnumType attribute. The value-name pairs are represented by the EnumItem class which holds the enumeration value in the Value property. Since the combobox items are not the enumeration values directly, it is important to set the SelectedValuePath to Value and bind the underlying property to the SelectedValue (instead of SelectedItem).

In this basic example, the EnumItemList instance is created directly as the ItemsSource for this particular combobox, but in most applications, it is more efficient and maintainable to reuse the same list by defining it as a resource as shown in the examples below. In a Model-View-ViewModel (MVVM) application, the EnumItemList can optionally be created programmatically and accessed through a binding to a static ViewModel property.

In this case, a similar result can be achieved by using just an ObjectDataSource, but that requires more XAML code and does not offer the localization and customization support described in the next sections. With EnumItemList, the items are also automatically sorted alphabetically by default - more about this later.

Text customization in XAML

As mentioned before, EnumItemList is a collection of EnumItems. To customize the text displayed, you can simply add new EnumItems directly in the XAML code like this:

<e:EnumItemList EnumType="{x:Type e:FontStyles}" >
    <e:EnumItem Value="BoldItalic">Bold and Italic</e:EnumItem>
    <e:EnumItem Value="{x:Null}">(Select style)</e:EnumItem>
</e:EnumItemList>

In the second line, we change the name of the BoldItalic value to a more user-friendly form. This will replace the old display text for this particular enumeration value. The other enumeration values will be shown using their code names as before. In this example, we also add a new named list option for the null value to support a nullable backing property. This is not easily achieved using an ObjectDataSource.

The value of the Value property is checked at both design and run-time, so in case the enumeration value is not recognized, the Visual Studio WPF designer will report an error.

By specifying the texts to display for the enum values in EnumItems as shown above, they can conveniently be localized in the same way as all other XAML content, as described in the MSDN page: WPF Globalization and Localization Overview.

Text customization using code attributes

In the same way as described in Sacha Barber’s article, EnumItemList also looks for DescriptionAttribute on the enum values and uses their descriptions as enum text instead of the enum entry code name if found:

public enum FontStyles
{
    Normal,
    Bold,
    Italic,
        
    [Description("Bold and italic")]
    BoldItalic
}

The enumeration value text specified in XAML will take precedence over the text from the DescriptionAttribute. Localization of enum description specified by code attributes can be achieved by using a LocalizedDescriptionAttribute as described in Sacha’s article.

A limitation of the attribute based technique to customize enum value visualization is that it cannot be customized by you if the enum type is declared in the .NET Framework or a third-party library. Specifying customized and localized text in XAML using EnumItemList will on the other hand work for any enum type, including those defined in the framework and third-party libraries. In these cases, you can either specify all custom item texts in XAML as shown above, or specify the enum representations as assembly resources as discussed below.

Customizing enum texts via a custom type converter

EnumItemList also supports customized string conversion specified by using a custom TypeConverter for the enum type:

[TypeConverter(typeof(MyEnumConverter))]
public enum MyEnum { … }

The custom converter (MyEnumConverter) must then derive from System.ComponentModel.TypeConverter and should then return custom enum texts from its ConvertTo method. In this way, localized names can be read from other sources. However, EnumItemList already supports an easier way to read localized enum texts from assembly resource files as described next.

Reading enum items from assembly resources

EnumItemList inherently supports reading enumeration value representations from assembly resource files, and this is not limited to text. Icon and image resources are also supported out of the box. To enable this, you simply create a type-safe resource file in Visual Studio with resources named as the EnumType and the enum item name separated by an underscore, for instance FontStyles_Bold. The resource can be a string, icon, or image. To activate this, you then simply have to set the ResourceType property to the resource type:

<e:EnumItemList EnumType="e:FontStyles" ResourceType="res:Resources" />

Any resource found will replace the current DisplayValue for the EnumItem and thanks to a custom type converter for the EnumItem class, non-textual resource enum representations will be displayed correctly without the need for data templates. Missing resources will not result in a run-time error, but in debug builds, a debug trace message will be issued to inform about this fact.

Showing drawings and images

By simply setting the content property DisplayValue of EnumItems to Drawing-derived types, you can show line-art (GeometryDrawing), images (ImageDrawing), and even videos (VideoDrawing) as enum item representations.

<e:EnumItem Value="Italic" Text="Italic" >
    <GeometryDrawing Geometry="M 5,0 L 0,16" >
        <GeometryDrawing.Pen>
            <Pen Brush="Black" Thickness="2" />
        </GeometryDrawing.Pen>
    </GeometryDrawing>
</e:EnumItem>

Using EnumItems in custom data templates

In a modern WPF application, it is likely that you want to illustrate each enum value in a richer fashion than just a plain text or image. The EnumItem’s content property DisplayValue can be set to an arbitrary object and be bound to an arbitrary dependency property in a customized DataTemplate. This may be the case if you, for instance, have a requirement to show each enum value with a distinct color:

<Window.Resources>
   <e:EnumItemList x:Key="valueStatusEnumList" EnumType="{x:Type e:ValueStatus}" >
        <e:EnumItem Value="Ok" DisplayValue="Green" Text="Ok" />
        <e:EnumItem Value="Warning" DisplayValue="Orange" Text="Warning" />
        <e:EnumItem Value="Error" DisplayValue="Red" Text="Error" />
    </e:EnumItemList>
</Window.Resources> 
...
<ComboBox ItemsSource="{StaticResource valueStatusEnumList}" 
        SelectedValue="{Binding Status}" SelectedValuePath="Value" >
    <ComboBox.ItemTemplate>
        <DataTemplate DataType="{x:Type e:EnumItem}" >
            <TextBlock Text="{Binding Text, Mode=OneTime}" 
                 Background="{Binding DisplayValue, Mode=OneTime}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>            
</ComboBox>

Here we have an enum type called ValueStatus with the values Ok, Warning, and Error. Instead of merely defining a text, we use the EnumItem’s DisplayValue property to define the color to be used, and then we redefine the ItemTemplate to use that value for the Background property. The result is shown below:

EnumItemList/ColoredItems.GIF

When using an EnumItemList to show non-textual content, the EnumItem’s Text property is handy to provide an alternative textual representation for each item. In the example above, the Text property is used in the data template to display it. Setting Text also allows the user to select an item using the keyboard by typing the first letter when using non-textual DisplayValues.

Arbitrary UIElements as enum representations

In some scenarios, you might have the requirement to show a complex view consisting of several user interface elements (e.g., icon, text, and description) for each enumeration value. This is also supported by EnumItemList by specifying a DataTemplate for each value, like this:

<Windows.Resources>
   <e:EnumItemList x:Key="fontStylesList" EnumType="{x:Type e:FontStyles}" >
        <e:EnumItem Value="Bold" Text="Bold" >
            <DataTemplate>
                <TextBlock FontWeight="Bold" 
                   Text="{Binding Text, Mode=OneTime}"></TextBlock>
            </DataTemplate>
        </e:EnumItem>
        <e:EnumItem Value="BoldItalic" Text="Bold and Italic" >
            <DataTemplate>
            <TextBlock FontWeight="Bold" 
              FontStyle="Italic">Bold and Italic</TextBlock>
            </DataTemplate>
        </e:EnumItem>
        <e:EnumItem Value="Italic" Text="Italic" >
            <DataTemplate>
            <TextBlock FontStyle="Italic">Italic</TextBlock>
            </DataTemplate>
        </e:EnumItem>
        <e:EnumItem Value="Normal" Text="Normal" >
            <DataTemplate>
                <TextBlock>Normal</TextBlock>                   
            </DataTemplate> 
        </e:EnumItem>
    </e:EnumItemList>
</Windows.Resources>

<ComboBox ItemsSource="{StaticResource fontStylesList}" 
          SelectedValue="{Binding FontStyle}" 
          SelectedValuePath="Value" >
    <!--  <ComboBox.ItemTemplate>
        <DataTemplate >
            <ContentPresenter ContentTemplate="{Binding DisplayValue, Mode=OneTime}" />
        </DataTemplate> 
    </ComboBox.ItemTemplate>  -->
</ComboBox>

In this case, we can bind the EnumItem’s DisplayValue to the ContentPresenter's ContentTemplate in the ComboBox’s ItemTemplate. As the comments in the code-snippet above shows, it is however not necessary to specify this explicitly. Thanks to a custom type converter for EnumItem, any DataTemplate is applied automatically using the default content template containing ContentPresenter. It is also possible to skip the data template part and simply specify the UIElements directly as the EnumItem’s content, but then you cannot use bindings to EnumItem properties to get localized texts from resource files for instance. In addition, when the enum item is to be reused, the UIElement has to be cloned which is not as quick as using a DataTemplate. The result is shown below:

EnumItemList/EnumItemList.GIF

In the example above, the only things that differ between the enum item templates are the FontStyle and FontWeight property values. In this case, we could instead have defined a style for each enum item with setters for those properties. EnumItem inherently supports Style content by applying them to a TextBlock showing the Text content without the need for specifying an ItemTemplate.

Provided that the complex enumeration list is only to be shown in a single list control, you may actually skip the EnumItemList altogether and get the same result by creating a control for each enumeration value and use DataContext to bind to the value:

<ComboBox SelectedValuePath="DataContext" SelectedValue="{Binding FontStyle}">
    <TextBlock DataContext="Normal">Normal</TextBlock>
    <TextBlock DataContext="Bold" FontWeight="Bold">Bold</TextBlock>
    <TextBlock DataContext="Italic" FontStyle="Italic">Italic</TextBlock>
    <TextBlock DataContext="BoldItalic" FontStyle="Italic" 
        FontWeight="Bold">Bold And Italic</TextBlock>
</ComboBox>

This solution requires much less code but the items cannot easily be reused in other contexts. Another limitation is that the items cannot easily be sorted according to a localized text. With EnumItemList, sorting by localized text is the default behavior, which can be customized as described in the next section.

Sorting the items

In most scenarios, it is most user-friendly to sort the enum items in alphabetical order. With EnumItemList, this is the default behavior. To customize sorting, you can set the SortBy property in XAML, to sort by the Text, DisplayValue, or Value property. More advanced sorting can be achieved by setting the Comparer property to your own IComparer<EnumItem> implementation.

Using EnumItemList as a value converter

So far all examples have been for customizing a combo box enumeration display. The same approach can of course be used for any other ItemsControl, including ListBox. Sometimes it is useful to visualize enumeration values outside a list control context. To support such scenarios, EnumItemList is able to act as a value converter on bindings. In the example below, the value of the enum property Status is visualized as a static (non-interactive) control with a different background color for each enum value.

<ContentControl Content="{Binding Status,Converter={StaticResource valueStatusEnumList} }" >
    <ContentControl.ContentTemplate>
        <DataTemplate DataType="{x:Type e:EnumItem}" >
            <TextBlock Text="{Binding Text, Mode=OneTime}" 
               Background="{Binding DisplayValue, Mode=OneTime}" />
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

As you may have noticed, the DataTemplate is identical to the one used for the combo box items before. When the EnumList is specified as a converter for a binding, it will convert enumeration values to the corresponding EnumItem instance, and thus all its properties can be used in the data template.

Note: As reported in the forum the WPF designer in Visual Studio may report errors when using EnumItemList as a value converter in a data template and other specific circumstances. However, this does not affect the ability to build and the functionality during run-time. This seems to be related to the WPF designer problem reported here. It might be possible to solve this minor design-time issue by rewriting EnumItemList so that it does not derive from ObservableCollection (i.e. not implement IList), but such major rework may brake other usages.

Using EnumItemList for non-enum types

Interestingly, EnumItemList is implemented to also be useful for types that are not declared as enums, including integers, strings, and custom objects. An example can be error codes from some external system that is not possible or appropriate to wrap in an enum type. To map the error codes to more user-friendly and localizable descriptions directly in XAML, you can use the EnumItemList as shown below:

<Window.Resources>
    <e:EnumItemList x:Key="errorCodeList" 
              EnumType="{x:Type sys:Int32}" SortBy="Value" >
        <e:EnumItem Value="1001">File not found.</e:EnumItem>
        <e:EnumItem Value="1002">Directory not found.</e:EnumItem>
        <e:EnumItem Value="1003">Not sufficient disc space.</e:EnumItem>
        <e:EnumItem Value="1004">General read error.</e:EnumItem>
        <e:EnumItemList.DefaultItem>
            <e:EnumItem >Unrecognized error.</e:EnumItem>
        </e:EnumItemList.DefaultItem>
    </e:EnumItemList>
    <DataTemplate x:Key=errorCodeTemplate >
       <StackPanel Orientation="Horizontal" >
                <ContentPresenter Content="{Binding Value, Mode=OneTime}" Width="35" />
                <ContentPresenter Content="{Binding DisplayValue, Mode=OneTime}" />
            </StackPanel>
    </DataTemplate>
</Windows.Resources><ComboBox SelectedValuePath="Value" SelectedValue="{Binding ErrorCode}" 
          ItemsSource="{StaticResource errorCodeList}"
          ItemTemplate=”{StaticResource errorCodeTemplate}” />

<ContentControl Content="{Binding ErrorCode, Converter={StaticResource errorCodeList}}" 
                ContentTemplate=”{StaticResource errorCodeTemplate}” />

In this way, EnumItemList can serve as a very general XAML switch/case statement. As illustrated in the code-snippet above, there is also a DefaultItem EnumList item which serves as a template for the item returned on conversion whenever no other matching item is found. The Value property in the item returned will then be the actual value, so if the ErrorCode is set to a value not enlisted, the default message will be shown. The result is shown below:

EnumItemList/NonEnumItems.GIF

Implementation details

EnumItemList

To allow using EnumItemList instances as ItemSources directly and to add items using XAML, EnumItemList has to implement IList. We also want to do some type-checking when adding items. I chose to just simply derive the EnumItemList class from the ObservableCollection to get support for change notifications and thereby support for adding or changing items after binding has occurred. The list is populated when the EnumType property is set to support defining it in XAML:

public class EnumItemList : ObservableCollection<EnumItem>, IValueConverter
{
    public Type EnumType
    {
        get { return m_enumType; }
        set
        {
            m_enumType = value;
                
            // Cache type converter to increase performance.
            m_converter = TypeDescriptor.GetConverter(m_enumType);

            // Convert any existing item values to type.
            foreach (EnumItem item in this)
            {
                ConvertValueToEnumType(item);
            }

            //For enum types, fill list with values and their names 
            if (m_enumType.IsEnum)
            {
                // For all public static fields, i.e. all enum values
                foreach (FieldInfo field in m_enumType.GetFields(
                         BindingFlags.Public | BindingFlags.Static))
                {
                    object fieldValue = field.GetValue(null);

                    // Look for DescriptionAttributes.
                    object[] descriptions = 
                      field.GetCustomAttributes(typeof(DescriptionAttribute), true);

                    string displayName;
                    if (descriptions.Length > 0)
                    {
                        displayName = ((DescriptionAttribute)descriptions[0]).Description;
                    }
                    else
                    {
                        try
                        {
                            // Use type converter to support enums with custom TypeConverters.
                            displayName = m_converter.ConvertToString(fieldValue);
                        }
                        catch (Exception ex)
                        {
                            displayName = field.Name;
                        }
                    }
                    EnumItem item = new EnumItem() { Value = fieldValue, DisplayValue = displayName };
                        
                    if (IndexOfValue(item.Value) < 0)
                    // Do not add item if it already exists.
                    {
                        Add(item);
                    }
                }
            }
        }
    }

    public void ConvertValueToEnumType(EnumItem item)
    {
        if (m_enumType != null && item.Value != null && 
           (m_enumType.IsAssignableFrom(item.Value.GetType()) == false))
        {
            item.Value = 
              m_converter.ConvertFrom(null, CultureInfo.InvariantCulture, item.Value);
        }
    }
…
}

EnumItem

The EnumItem class is a plain container class with the properties Value, DisplayValue, and Text:

[TypeConverter(typeof(EnumItemTypeConverter))]
[Serializable()]
[ContentProperty("DisplayValue")]
public class EnumItem
{
    [Localizable(false)]
    public Object Value { get; set; }

    [Localizable(true)]
    public Object DisplayValue { get; set; }

    private String m_text;

    [Localizable(true)]
    public String Text
    {
        get {
            return m_text ?? ((DisplayValue != null) ? DisplayValue.ToString() : 
                     ((Value != null ? Value.ToString() : null)));
        }
        set { m_text = value; }
    }

    public override string ToString()
    {
        return Text;
    }
}

The ContentProperty attribute is used to define that it is the DisplayValue that is set to the XML element content. ToString() is overridden to return the textual representation when bound to String-type properties and when text search is performed.

EnumItemTypeConverter

To support displaying non-textual content without having to specify an ItemTemplate, the EnumItem has a custom type converter that is able to provide a UIElement to be shown in a ContentPresenter context, which includes ComboBoxItems with default control templates. If you have not specified a template, the ContentPresenter will ask the converter for a UIElement. Based on the type of DisplayValue, the converter will return a suitable UIElement as shown in the code-snippet below.

public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
    return destinationType == typeof(UIElement) || destinationType == typeof(String);
}

public override object ConvertTo(ITypeDescriptorContext context, 
       CultureInfo culture, object value, Type destinationType)
{
    EnumItem item = value as EnumItem;
    if (item != null)
    {
        if (destinationType == typeof(String))
        {
            return item.ToString();
        }
        if (destinationType == typeof(UIElement))
        {
            object displayValue = item.DisplayValue;
            if (displayValue == null || displayValue is String)
            {
                TextBlock textBlock = new TextBlock();
                textBlock.Text = item.ToString();
                return textBlock;
            }
            else if (displayValue is UIElement)
            {
                if (VisualTreeHelper.GetParent((UIElement)displayValue) != null)
                {
                    // Clone UIElement to allow it to be used several times.
                    string str = XamlWriter.Save(displayValue);
                    StringReader sr = new StringReader(str);
                    XmlReader xr = XmlReader.Create(sr);
                    UIElement ret = (UIElement)XamlReader.Load(xr);
                    return ret;
                }
                else
                {
                    return displayValue;
                }
            }
            if (displayValue is DataTemplate)
            {
                ContentPresenter presenter = new ContentPresenter();
                presenter.Content = item;
                presenter.ContentTemplate = (DataTemplate)displayValue;
                return presenter;
            }
            else if (displayValue is Style)
            {
                TextBlock textBlock = new TextBlock();
                textBlock.Style = (Style)displayValue;
                textBlock.Text = item.ToString();
                return textBlock;
            }
            // Support for other DisplayValue types not shown in article.
        }
        throw new InvalidOperationException(
              "Unable to convert item value to destination type.");
    }
    return null;
}

As shown in the code-snippet above, UIElements as direct display values are supported. To be able to use it in several locations, the UIElement is cloned as necessary using XamlWriter. If performance matters, it is however recommended to wrap the element in a DataTemplate instead.

Conclusions

The EnumItemList presented here has been designed to be easy to use for the developer and cover up for many situations where enum values are to be presented with good localizability support and support for non-textual content. It can even be useful as a general XAML switch statement with non-enum types as well.

I am especially glad that I found a way to support showing non-textual content such as DataTemplates, Drawings, and icon resources without having to specify a custom template in XAML. The solution was a custom TypeConverter which provides a suitable UIElement when shown in a ContentPresenter context.

EnumItemList can not be used directly in Silverlight since it uses TypeDescriptor and supports non-textual resources which Silverlight does not support. However, it should be straightforward to strip of this functionality to get it to work in Silvelight too, at least for localizable textual enum representations.

Hopefully it will be useful for you to create applications that show enum values as the end-user wants to see them.

History

November 30, 2011

  • Initial version published.

July 7, 2012

  • Updated source code with support for Image control and ImageSource properties as discused in the article forum below. Also some minor bug fixes.

Mars 3, 2014

  • Updated code with potential fix for WPF designer errors and converter improvement according to Thoits (Version 1.2).

Mars 12, 2014

  • Updated article source code (version 1.3) and added note about WPF designer errors.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here