65.9K
CodeProject is changing. Read more.
Home

WPF Listbox and Combobox MVVM "Binding" Enum

Jan 6, 2016

CPOL

1 min read

viewsIcon

25243

downloadIcon

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:

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:

<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:

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.

public class EnumListItem
{
    public object Value { get; set; }
    
    public string DisplayValue { get; set; }
}
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".

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:

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:

<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>

Enjoy and I hope this tip will help someone.