Click here to Skip to main content
15,886,873 members
Articles / Desktop Programming / WPF
Tip/Trick

WPF Listbox and Combobox MVVM "Binding" Enum

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
6 Jan 2016CPOL1 min read 24.3K   512   8   2
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)


Written By
Software Developer (Senior) Self employed
Portugal Portugal
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
NewsThis works in .NET Core 3.0! Pin
raifordb12-Dec-19 5:12
raifordb12-Dec-19 5:12 
SuggestionMinor improvements - ObservableCollection not necessary Pin
kct7-Jan-16 23:57
kct7-Jan-16 23:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.