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 enum
s 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.