WPF Listbox and Combobox MVVM "Binding" Enum

Datetime:2016-08-23 01:26:08          Topic: MVVM Model  WPF           Share

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, ex: " 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 with the started with same name of enum ex:" CountryTypeResources.resx "

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

And create more for each Culture that you want to use ex: 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 transform the Enum into a ObservableCollection for binding

On 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 use also a static extension class for get 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 that 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>

Enjoi and I hope this tip can help anyone





About List