First of all, we create MarkupExtension by deriving from MarkupExtension class. MarkupExtensions are quite useful, especially in situations where we have static data (such as resource file, or enum) and we want to do something with the data and return the outcome. All the heavy lifting will be happening in the MarkupExtension's ProvideValue method which we need to override.
Code Snippet
- public class EnumValuesExtension : MarkupExtension
- {
- private Type _enumType;
- private String _resourceName;
- private bool _addEmptyValue = false;
- // Enumeration type
- public Type EnumType
- {
- set { _enumType = value; }
- }
- // Name of the class of the .resx file
- public String ResourceName
- {
- set { _resourceName = value; }
- }
- // Add empty value flag
- public Boolean AddEmptyValue
- {
- set { _addEmptyValue = value; }
- }
- public EnumValuesExtension()
- {
- }
Next, I've added these 3 properties, which are pretty self explanatory and decorated with comments, they provide the basis for the data that goes into the extension, and are settable from XAML. It is possible to use the constructor straight in XAML, but it's much more comfortable to do it using these properties, hence the constructor remains parameterless.
Code Snippet
- public override object ProvideValue(IServiceProvider serviceProvider)
- {
- // Enumeration type not passed through XAML
- if (_enumType == null)
- throw new ArgumentNullException("EnumType (Property not set)");
- if (!_enumType.IsEnum)
- throw new ArgumentNullException("Property EnumType must be an enum");
- // Bindable properties list
- List<dynamic> list = new List<dynamic>();
- if (!String.IsNullOrEmpty(_resourceName))
- {
- // Name of the resource class
- Type type = Type.GetType(_resourceName);
- if (type == null)
- throw new ArgumentException(
- "Resource name should be a fully qualified name");
- // We iterate through the Enum values
- foreach (var enumName in Enum.GetNames(_enumType))
- {
- string translation = string.Empty;
- var property = type.GetProperty(enumName,
- BindingFlags.Static |
- BindingFlags.Public |
- BindingFlags.NonPublic);
- // If there's not translation for specific Enum value,
- // there'll be a message shown instead
- if (property == null)
- translation = String.Format(
- "Field {0} not found in the resource file {1}",
- enumName,
- _resourceName);
- else
- translation = property.GetValue(null, null).ToString();
- list.Add(GetNamed(translation, enumName));
- }
- // Adding empty row
- if (_addEmptyValue)
- list.Add(GetEmpty());
- return list;
- }
- // If there's no resource provided Enum values will be used
- // without translation
- foreach (var enumName in Enum.GetNames(_enumType))
- list.Add(GetNamed(enumName, enumName));
- if (_addEmptyValue)
- list.Add(GetEmpty());
- return list;
- }
Above code is mostly using reflection to shuffle properties around - get values of the enumeration and their translations. Also using dynamic objects to make a bindable context for our ViewModel to bind to. For that we need last 2 methods.
Code Snippet
- // Create one item which will fill our ComboBox ItemSource list
- private dynamic GetNamed(string translation, string enumName)
- {
- // We create a bindable context
- dynamic bindableResult = new ExpandoObject();
- // This dynamically created property will be
- // bindable from XAML (through DisplayMemberPath or wherever)
- bindableResult.Translation = translation;
- // We're setting the value, which will be passed to SelectedItem
- // of the ComboBox
- bindableResult.Enum = enumName;
- return bindableResult;
- }
- // Create one empty item which will fill our ComboBox ItemSource list
- private dynamic GetEmpty()
- {
- dynamic bindableResult = new ExpandoObject();
- bindableResult.Translation = String.Empty;
- bindableResult.Enum = null;
- return bindableResult;
- }
Finally, we can call this code from XAML, it is worth noting that due to how GetType method works we will need a fully qualified path to the resource class. In parameters below we can see properties being set, EnumType to the Type of the enum which is going to fill the Combo, and ResourceName to the fully qualified name of the class (the name of the project/module, in this case "Mod.Ule" is additionally added after the comma). SelectedType is a property in the ViewModel of the same type as the enum passed into the extension, and it is all we need in our VM to make this work. This mechanisms works with DataTemplates as well, all you'd need to do is bind to Translation instead of using DisplayMemberPath.
Code Snippet
- <ComboBox Height="30" Width="100" VerticalAlignment="Top"
- SelectedValuePath="Enum" DisplayMemberPath="Translation"
- ItemsSource="{Binding Source={Extensions:ValuesEnum
- EnumType=Extensions:Types,
- ResourceName='Mod.Ule.Resources.Strings.Enums.Resource1,Mod.Ule'}}"
- SelectedValue="{Binding SelectedType}"/>
No comments:
Post a Comment