Monday, November 7, 2011

Binding ENUM to WPF control - ComboBox

I believe, I explored quite a bit on finding the best approach for my requirement of binding Enum to WPF ComboBox and thought to share the gradual steps here for a one-stop place which hopefully will help similar minded technical enthusiast ..

The simplest approach:

You want to just bind Enum as it appears (verbatim) to the WPF combo ..
Let's define a sample enum as follows:

namespace
{
   public enum
TestEnum   {
      String1,
      String2,
      String3,
      String4
   }
}

All we now need is a way to leverage Enum - GetValues method to provide a list of values that lets you bind directly to the ComboBox via ItemSource.

<ObjectDataProvider x:Key="dataFromEnum"
MethodName="GetValues" ObjectType="{x:Type sys:Enum}" >
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:TestEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>


Reference URL: http://msdn.microsoft.com/en-us/library/system.enum.getvalues.aspx 

Bind your Controls as below and you are all set!

<ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
SelectedIndex="0"
Name="testComboBox" />

Monica, a WPF Development Girl, has got it quite well documented - this simplest approach here


Second Approach:

The simplest approach listed above is well and good as long as you don't care a friendly, descriptive name presented as list for ComboBox whilst maintaining underlying Enum throughout your application layers ..

Step 1: Adorn your Enum with some sort of Description attribute

namespace EnumExperimentals
{
    public enum TestEnum
    {
        [LocalizableDescription(@"String1", typeof(Resource))]
        String1,

        [LocalizableDescription(@"String2", typeof(Resource))]
        String2,

        [LocalizableDescription(@"String3", typeof(Resource))]
        String3,

        [LocalizableDescription(@"String4", typeof(Resource))]
        String4,
    }
}

Step 2:  Override the description attribute with the code as below.. (this is pretty much ctrl-c/ctrl-v from Sacha Barber's sharing on code project

http://www.codeproject.com/KB/WPF/FriendlyEnums.aspx

        public override string Description
        {
            get
            {
                if (!_isLocalized)
                {
                    ResourceManager resMan =
                         _resourcesType.InvokeMember(@"ResourceManager",
                         BindingFlags.GetProperty | BindingFlags.Static |
                         BindingFlags.Public | BindingFlags.NonPublic,
                         null,
                         null,
                         new object[] { }) as ResourceManager;

                    CultureInfo culture =
                         _resourcesType.InvokeMember(
                         @"Culture",
                         BindingFlags.GetProperty | BindingFlags.Static |
                         BindingFlags.Public | BindingFlags.NonPublic,
                         null,
                         null,
                         new object[] { }) as CultureInfo;

                    _isLocalized = true;

                    if (resMan != null)
                    {
                        DescriptionValue =
                             resMan.GetString(DescriptionValue, culture);
                    }
                }

                return DescriptionValue;
            }
        }

Step 3: Add one of the coolest feature of WPF (ValueConverter) and you're pretty much there ..

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null)
            {
                    FieldInfo fi = value.GetType().GetField(value.ToString());

                    if (fi != null)
                    {
                        var attributes = (LocalizableDescriptionAttribute[])fi.GetCustomAttributes(typeof(LocalizableDescriptionAttribute), false);

                        return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
                    }
            }

            return string.Empty;
        }


Step 4: Now just get your XAML beautified and it's all done :-)

        <ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
                    SelectedItem="{Binding Path=TestEnum.TestEnum}"
                    SelectedIndex="0" >
           
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <Label 
                        Content="{Binding Path=.,Mode=OneWay, Converter={StaticResource enumItemsConverter}}"
                        Height="Auto"
                        Margin="0"
                        VerticalAlignment="Center"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

To further explore this second approach, this is a must read!

Third Approach:

So, the second approach is all good if I really have to concern myself with the localization – and not necessarily just the friendly descriptive names for each Enum. So I set on my exploration path further and discovered, in my mind, a much cleaner and neater approach of achieving my requirement. (Recall: I want to display friendly names for the Enums while preserving their underlying values across the usage of application)

Atul has documented this approach here on Infosys Blog.
I actually implemented in one of my current projects and the code is pretty much as listed below.

Step 3.1: Your sample Enum goes as below.

    public enum SearchCriterion
    {
StartsWith,
EndsWith,
Unlike,
Contains
    }


Step 3.2: Wire-up your Utility class to yield you a dictionary collection for friendly enum names. I actually did not like this approach, just a bit that I have to have friendly names depicted in my code-behind.

    internal class myUtility
    {
        static myUtility()
        {
            _searchCriteria = new Dictionary<SearchCriterion, string>()
            {
                {SearchCriterion.StartsWith, "Starts With"},
                {SearchCriterion.EndsWith, "Ends With"}
                {SearchCriterion.Unlike, "NOT Like"}
                {SearchCriterion.Contains, "Contains"}
            };
        }

        private static Dictionary<SearchCriterion, string> _searchCriteria;

        public static Dictionary<SearchCriterion, string> SearchCriteria
        {
            get { return myUtility._searchCriteria; }
        }
    }

Step 3.3: Instantiate your Utility class in XAML like below, in your Resources section.

       <local: myUtility x:Key="theUtility" />

Step 3.4: And, finally wire-up the required code for binding with RadComboBox and we are all good.

<telerik:RadComboBox Name="cboSearchCriterion"
ItemsSource="{Binding Source={StaticResource tariffPickerUtility}, Path=SearchCriteria}"
SelectedIndex="0"
DisplayMemberPath="Value"
SelectedValuePath="Key"                                            
SelectionChanged="cboSearchCriterion_SelectionChanged" >
</telerik:RadComboBox>

The Approach:

In my pursuit (for happiness) for binding Enums to WPF Combo (now as you may have figured out, I’m actually using Telerik’s RadComboBox), just couple of days ago, while searching stuff on Telerik’s forum, I realized there is a much easier way to accomplish my current requirement (Recall: I want to display friendly names for the Enums while preserving their underlying values across the usage of application)

Telerik has already created an Utility EnumDataSource – that facilitates binding RadComboBox or a GridViewComboBoxColumn to an Enum.

The key is the following set of statements, from Telerik:

The EnumDataSource will return a collection of view models based on the Enum type supplied. Attributes such as DisplayAttribute and DescriptionAttribute will be read and stored in the view models, so that friendly names can be displayed in the combo-boxes.

In this approach, my sample Enum is like as below:

    public enum SearchCriterion
    {
        [Description("Starts With")]
        StartsWith,

        [Description("Ends With")]
        EndsWith,

        [Description("NOT Like")]
        Unlike,

        [Description("Contains")]
        Contains
    }

And, plug the code below in your code behind

            IEnumerable<EnumMemberViewModel> _searchCriteria = EnumDataSource.FromType<SearchCriterion>();
            cboSearchCriterion.ItemsSource = _searchCriteria;

All we needed here is to leverage the EnumDataSource as mentioned above and bind the control via ItemsSource property and all is goody good. J

No comments:

Post a Comment