Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

WPF Listbox and Combobox MVVM Binding Enum

6 Jan 2016CPOL1 min read 25.2K   512  
Another simple way to bind one Enumeration to a Combobox or to a Listbox

Introduction

I will try to explain the solution that I normally implement when I want to use a Enum with a Combobox or a ListBox also for different Cultures.

Using the Code

First, create a WPF Project, example: "WpfApplicationSample" and a folder named ViewModels, and inside that folder, a class named MainWindowViewModel.cs derived from INotifyPropertyChanged:

C#
public class MainWindowViewModel : INotifyPropertyChanged
{
	public event PropertyChangedEventHandler PropertyChanged;

	Boolean SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
	{
		if (object.Equals(storage, value))
   			return false;
		storage = value;
		OnPropertyChanged(propertyName);
		return true;
	}
	
	void OnPropertyChanged(String propertyName)
	{
		if (PropertyChanged != null)
			PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
	}
}

Now, edit MainWindow.xaml and change to this:

XML
<Window ... 
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:viewModels="clr-namespace:WpfApplicationSample.ViewModels"
	...
	d:DataContext="{d:DesignInstance viewModels:MainWindowViewModel, IsDesignTimeCreatable=True}"
	d:DesignHeight="400"
	d:DesignWidth="600"
	mc:Ignorable="d">

	<Window.DataContext>
		<viewModels:MainWindowViewModel/>
	</Window.DataContext>
	
	<Grid >
	</Grid>

</Window>

Second, create a class library for we create the enums and the resource dictionaries for each Culture that you want to use.

Inside of this last project, create one enum by example:

C#
public enum CountryType
{
	[DescriptionAttribute("")]
	None,
	
	[DescriptionAttribute("United States of America")]
	US,
        
	[DescriptionAttribute("Portugal")]
	PT,
        
	[DescriptionAttribute("United Kingdom")]
	GB
}

Inside of folder Properties, create a resource file which starts with same name of enum, example: "CountryTypeResources.resx".

Create another one for default Culture "CountryTypeResources.en.resx".

And create more for each Culture that you want to use example: for Portuguese-Portugal - "CountryTypeResources.pt-PT.resx":

Add on each resource file the same enum values, that you have on enum and on the column value put the value that you want to display, according to the culture.

Now, we need to create another class Library for transforming the Enum into a ObservableCollection for binding.

In that Library, add to classes named EnumListItem.cs and EnumListItemCollection.cs.

C#
public class EnumListItem
{
    public object Value { get; set; }
    
    public string DisplayValue { get; set; }
}
C#
public class EnumListItemCollection<T> : ObservableCollection<EnumListItem> where T : struct 
{
    readonly ResourceManager resourceManager;
    readonly CultureInfo cultureInfo;
    readonly Type enumType;
    readonly Type resourceType;
   
    public EnumListItemCollection() : this(CultureInfo.CurrentUICulture)
    {
    }
  
    public EnumListItemCollection(CultureInfo cultureInfo)
    {
        if (!typeof(T).IsEnum)
            throw new NotSupportedException(String.Format("{0} is not Enum!", typeof(T).Name));

        enumType = typeof(T);
        this.cultureInfo = cultureInfo;

        resourceType = GetResourceTypeFromEnumType();
        if (resourceType != null)
            resourceManager = new ResourceManager(resourceType.FullName, resourceType.Assembly);
          
        foreach (T item in Enum.GetValues(enumType))
            Add(new EnumListItem() { Value = item, DisplayValue = GetEnumDisplayValue(item) });
    }

    Type GetResourceTypeFromEnumType()
    {
        var manifestResourceName = 
        this.enumType.Assembly.GetManifestResourceNames().FirstOrDefault
			(t => t.Contains(this.enumType.Name)); 
        
        if (!String.IsNullOrEmpty(manifestResourceName))
            return Type.GetType(manifestResourceName.Replace(".resources", 
            	String.Empty), (a) => this.enumType.Assembly, 
            	(a,n,i) => this.enumType.Assembly.GetType(n, false, i));
            return null;
    }

    String GetEnumDisplayValue(T item)
    {
        var value = default(String);
        
        if (resourceManager != null)
            value = resourceManager.GetString(item.ToString(), cultureInfo);

        if (value == null)
        {
            var descriptionAttribute = (item as Enum).GetAttribute<DescriptionAttribute>();
            if (descriptionAttribute == null) 
                return item.ToString();
            return descriptionAttribute.Description;
        }
        
        return value;
    }
}

You can also use a static extension class for getting the "DescriptionAttribute".

C#
public static class AttributeExtentions
{
    public static TAttribute GetAttribute<TAttribute>(this Enum enumValue) where TAttribute : Attribute
    {
        var memberInfo = enumValue.GetType()
                            .GetMember(enumValue.ToString())
                            .FirstOrDefault();
            
        if (memberInfo != null)
            return (TAttribute)memberInfo.GetCustomAttributes(typeof(TAttribute), false).FirstOrDefault();
        return null;
    }
}

After, on WpfApplicationSample project, we add the references of those two Libraries.

Now edit MainWindowViewModel.cs and add:

C#
public class MainWindowViewModel : INotifyPropertyChanged 
{
    ...
    readonly EnumListItemCollection<CountryType> countries = new EnumListItemCollection<CountryType>();
    
    CountryType country;

    public EnumListItemCollection<CountryType> Countries
    { 
        get{ return countries;  } 
    }
    
    public CountryType Country
    {
        get { return country; }
        set { SetProperty(ref country, value); }
    }
    ...
}

and finally edit MainWindows.xaml to add the Combobox or the Listbox:

XML
<Window 
    ... 
	
	<Grid >
	
        <ComboBox HorizontalAlignment="Left"
                VerticalAlignment="Top" 
                Margin="12"
                Width="200"
                ItemsSource="{Binding Countries}"
                DisplayMemberPath="DisplayValue"
                SelectedValuePath="Value"
                SelectedValue="{Binding Country}" />

        <ListBox HorizontalAlignment="Left"
                VerticalAlignment="Top" 
                Margin="12,50"
                Width="200"
                ItemsSource="{Binding Countries}"
                DisplayMemberPath="DisplayValue"
                SelectedValuePath="Value"
                SelectedValue="{Binding Country}" />

	</Grid>
</Window>

Image 1

Enjoy and I hope this tip will help someone.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)