Enumeration Binding in Silverlight

by Dean 31. January 2009 10:57

A contractor colleague of mine (Phil Steel) had an interesting Silverlight problem yesterday. He wanted to populate a Silverlight ComboBox with an enumeration, and implement 2-way binding to a property on his Data class (i.e. binding his property to the ‘SelectedItem’ on the ComboBox.

His requirements were as follows:

  1. The solution must have full design-time support in Expression Blend
  2. The code must give the developer an opportunity to create metadata for the enumeration that can be used for ‘user friendly’ visual values in the ComboBox
  3. There must be full 2-way binding, so no need to tap into the ‘SelectionChanged’ event to update the data object.
  4. Minimal C# code, with as much as possible being re-useable ‘as is’ for any enumeration.

Ok, so I googled around and only found fragments of a possible solution, so I decided to engineer one myself.

The details are below

Task 1 : Create reusable code that converts a .NET enumeration into a collection

 

There are 2 classes we need as part of this task, and the code is below

public sealed class EnumContainer
{
    public int EnumValue { get; set; }
    public string EnumDescription { get; set; }
    public object EnumOriginalValue { get; set; }
    public override string ToString()
    {
        return EnumDescription;
    }
 
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        return EnumValue.Equals((int)obj);
    }
 
    public override int GetHashCode()
    {
        return EnumValue.GetHashCode();
    }
 
}
 
public class EnumCollection<T> : List<EnumContainer> where T : struct 
{
    public EnumCollection()
    {
        var type = typeof (T);
        if (!type.IsEnum)
            throw new ArgumentException("This class only supports Enum types");
        var fields = typeof (T).GetFields(BindingFlags.Static | BindingFlags.Public);
        foreach(var field in fields)
        {
            var container = new EnumContainer();
            container.EnumOriginalValue = field.GetValue(null);
            container.EnumValue = (int) field.GetValue(null);
            container.EnumDescription = field.Name;
            var atts = field.GetCustomAttributes(false);
            foreach (var att in atts)
                if (att is DescriptionAttribute)
                {
                    container.EnumDescription = ((DescriptionAttribute) att).Description;
                    break;
                }
            Add(container);
        }
        
    }
}

 

These 2 classes represent a bindable version of our enumeration. One class is an object that represents the enumeration values (EnumContainer), and the other is the collection.

(The part of the code above describing ‘CustomAttributes’ relates to the task below.

 

Task 2 (optional) : Add ‘user friendly’ metadata to your enumerations

 

This is a well known solution using the ‘DescriptionAttribute’ class

 

    public enum CustomerStatus
    {
        [Description("Not Yet Approved")]
        UnApproved,
 
        [Description("Pending Approval")]
        PendingApproval,
 
        [Description("Fully Approved")]
        Approved
    }

These descriptions will appear in your combobox

 

Task 3 : Create an IValueConverter to support 2-way binding of the ComboBox to the data object.

 

public class EnumValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                CultureInfo culture)
    {
        return (int) value;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
CultureInfo culture)
    {
        if (value == null)
            return null;
        if (value.GetType() == targetType)
            return value;
        return ((EnumContainer) value).EnumOriginalValue;
    }
}

 

Task 4 : Implement a solution to suit your needs

 

Ok, now that we have all the base code added to our project, it’s time to implement a solution to our problem. The only c# class that needs to be created is a simple collection class that inherits from EnumCollection and allows full designer support in Expression Blend

So here (for example) are our data classes that we will use to demonstrate our solution

1) Our example ‘Customer’ class

public class Customer
{
    public string CustomerName { get; set; }
    public CustomerStatus Status { get; set; }
}

2) Our example Enumeration (notice the use of the DescriptionAttribute, which gives the ComboBox user friendly test)

 

    public enum CustomerStatus
    {
        [Description("Not Yet Approced")]
        UnApproved,
 
        [Description("Pending Approval")]
        PendingApproval,
 
        [Description("Fully Approved")]
        Approved
    }

 

3) Our collection to provide Blend support (as you can see, its just a simple inheritor as XAML doesnt easily support generics).

public class CustomerStatusEnumeration : EnumCollection<CustomerStatus>
{
 
}

 

4) Here's an example of wiring up the data object in the code

 

private void PageLoaded(object sender, RoutedEventArgs e)
{
    customer = new Customer() { CustomerName = "Customer1", 
        Status = CustomerStatus.PendingApproval };
    DataContext = customer;
}

 

Task 5 : Wire it all up in the Xaml

<UserControl x:Class="TestEnum.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300" xmlns:TestEnum="clr-namespace:TestEnum" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
    <UserControl.Resources>
        <TestEnum:CustomerStatusEnumeration 
            x:Key="CustomerStatusEnumerationDS" 
            d:IsDataSource="True"/>
        <TestEnum:EnumValueConverter  
            x:Key="EnumConverter" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <TextBox Text="{Binding Mode=TwoWay, Path=CustomerName}" 
                 Margin="5" HorizontalAlignment="Left" VerticalAlignment="Top" />
        <ComboBox Name="Combo" HorizontalAlignment="Left" 
                  VerticalAlignment="Top" 
                  ItemsSource="{Binding Mode=OneWay, 
                        Source={StaticResource CustomerStatusEnumerationDS}}" 
                  SelectedItem="{Binding Status, Mode=TwoWay, 
                        Converter={StaticResource EnumConverter}}" 
                  Margin="5" Grid.Column="1"/>
        <Button Content="Update" Click="UpdateCustomer" Margin="5" 
                HorizontalAlignment="Left" 
                VerticalAlignment="Top" Grid.Column="2" />
    </Grid>
</UserControl>

An here’s a screenshot of the above solution

enumexample

As you can see, it’s a very simple and effective solution. And should we require to wire up a second enum to our Silverlight app then the only class we’d need to create (assuming the enum already exists) is the empty collection class as described above.

 

I hope you like the solution, any comments about how to improve it then please let me know.

 

Dean

Tags: , ,

WCF | Silverlight | DataBinding

Comments


February 2. 2009 06:21
trackback
Trackback from Community Blogs

Silverlight Cream for February 01, 2009 -- #504


February 3. 2009 09:57
Justin Angel
Cool sample of the power of IValueConverters, thanks for taking the time to post it.

One thing though, from an architectural standpoint, I'm not sure what I feel about overloading Class duties unto Enums. Translating a selected enum value to a UI experience sounds like something the UI layer needs to be concerned about and not the logic layer (the Enum itself). It might work for this one off blog sample, but convoluted architecture like this falls apart in real world apps.

Here's a good example, you hard-coded english strings in the enum attributes, what happens when we want to localize our app to many languages? You'll have to extend the support mechanism to a point where it's doing real business logic.

All and all, for this example I'd refactor the Enum to class with an ID and Text properties. Don't get me wrong, this is an awesome sample and way to go for building it, I'd just be weary about using this specific approach in real world apps. Savvy?

-- Justin Angel


February 3. 2009 10:00
Dean Chalk
Hi Justin

I agree with you about architecture, and wouldn’t design an object graph for a ‘Customer’ that included an enumeration value, because then you have a code dependency in your data layer. I used the scenario as an example, but it definitely lacked any real-life validity.

The specific scenario my colleague and I were trying to solve, is where we were creating a UI widget to build expression trees, so that we could apply filtering support to  a datagrid we were customising. The widget would then translate the expression trees to XML and persist them to isolated storage, then the user could load them back up (translating the XML back to an expression tree). We are using an enumeration for the type of operation (equal, leas than, more than etc), that we use in XML translations.

Thanks again for your time to read my post, and the great comments

Dean


United States Allen Newton 
February 3. 2009 19:19
Allen Newton
Enums are trouble for the database layer, granted.  Also, putting the resposibility for managing enums with a class object is debatable.  However, this is a great post on populating a combo box in Silverlight with an enum, essentially.  Real world developers have asked me how to bind an enum to a Silverlight combobox and here is a good way.  I would add that to overcome the localization limitation, there are several options.  1. A custom attribute that includes a culture id. Add an attribute for each language text for "description" on the enum. 2. Use a Dictionary object to store the key value pairs for each enum value and each locale.  3.  Use a resx file to store the enum name and value pairs.  There would be one resx for each language (e.g. status.resx, status.fr.resx, etc).  There is an article on this solution at Silverlight.net by Nikolay Raychev.  The change in code above would be to look up the text value from the resource file instead of looking and the Description attribute.  Which should be a minor change really.  Thanks for the post!!!


February 3. 2009 19:56
Dean Chalk
Thanks for your great comments Allen. My preference for localisation would be to go down the resources route like you suggest. As you said, it would only be a minor change.

Thanks again

Dean


February 3. 2009 20:06
Phil Steel
Mate

You’re a legend – great solution!

Phil


February 7. 2009 09:03
pingback
Pingback from silverlight-travel.com

Silverlight Travel » Enumeration Binding in Silverlight


Hungary Zákányi Balázs 
February 9. 2009 17:25
Zákányi Balázs
Thank for the great article.


Hungary Zákányi Balázs 
February 9. 2009 18:23
Zákányi Balázs
but it doesn't work by the convertback method... Frown


February 10. 2009 07:32
Dean Chalk
Hi Zákányi

The ConvertBack was tested and found OK. This solution only supports integer based enumerations, and I doubt if it supports enums that are used with the flags attribute for binary operations.

Send me your project file and I'll take a look if it helps

Dean


February 16. 2009 04:17
Stephen Price
Can't you just create a DataTemplate for the ComboBoxItems and then bind to {Binding} and let the enum .ToString() sort it out? I've done that with a listbox so it should work with a ComboBox too?

cheers,
Stephen


February 16. 2009 11:04
Dean Chalk
An enum isnt IEnumerable, so cannot be the source for a collection binding, so you'd still need a collection class to hold enum values.

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading



Most comments

Tom Tom
1 comments
Derek Lakin Derek Lakin
1 comments
gb United Kingdom
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010 ButtonChrome.com