Thursday 28 February 2013

Enum translation converter

         To follow up the previous entry on enums nad translations, I'll continue by adding some more functionality which helps in a similar manner. In some instances we needed to display an enum declared in the ViewModel  in a textbox or a column - specifically we  needed the enum's translation,  and that's where this converter came into play.

Code Snippet
  1. public class TranslateEnumConverter : MarkupExtension, IValueConverter
  2. {
  3.     public static TranslateEnumConverter _converter = null;                  

  4.     public TranslateEnumConverter()
  5.     {        
  6.     }
  7.  
  8.     public object ConvertBack(object value, Type targetType, object     parameter, System.Globalization.CultureInfo culture)
  9.     {
  10.         throw new NotImplementedException();
  11.     }
  12.  
  13.     public override object ProvideValue(IServiceProvider serviceProvider)
  14.     {
  15.         if (_converter == null)
  16.         {
  17.             _converter = new TranslateEnumConverter();
  18.         }
  19.         return _converter;
  20.     }
  21. }

          Let's start with nearly ordinary Converter class above. What's a bit different is using MarkupExtension as one of the ways to allow adding converter to binding in XAML without the need of declaring it as a resource. The pretty straightforward use of ProvideValue allows us exactly this:

Converter={Converters:TranslateEnumConverter}


Converters is the namespace where all our converters reside.
The Convert method, as in any converter, returns the converted value from VM to View. In our case it gets the resource and the translated string of the enum and returns it.

Code Snippet
  1. public object Convert(object value, Type targetType,
  2.     object parameter, System.Globalization.CultureInfo culture)
  3. {
  4.     if (value == null)
  5.         return String.Empty;
  6.  
  7.     if (parameter == null)
  8.         throw new ArgumentNullException("parameter");
  9.  
  10.     String inputParam = parameter.ToString();
  11.     String resourceName = String.Empty;
  12.   
  13.     resourceName = parameter.ToString();
  14.  
  15.     Type type = Type.GetType(resourceName);
  16.     string name = value.ToString();
  17.     if (type == null)      // Invalid resource name
  18.         throw new ArgumentException("Resource name 
  19.                   should be a fully qualified name");
  20.  
  21.     var property = type.GetProperty(name, BindingFlags.Static |
  22.         BindingFlags.Public | BindingFlags.NonPublic);
  23.     string translation = string.Empty;
  24.        
  25.     if (property == null) // With no corresponding Resx translation, there's an error message displayed
  26.         translation = String.Format("Field {0} not found in the resource file {1}", name, resourceName);
  27.     else  
  28.         translation = property.GetValue(null, null).ToString();
  29.  
  30.     return translation;
  31. }

            You can read the previous post's description to gain more knowledge about how the resource name is passed into the converter and parsed into parameters - beside that the code above should be pretty self-explanatory. With the code we've got we can call the converter from XAML, with Status being the Enum property from the ViewModel and fully qualified name of the resource class as the ConverterParameter:

Binding="{Binding Status,
Converter={Converters:TranslateEnumConverter}, 
ConverterParameter='Mod.Ule.Resources.Enums.EnumStrings,Mod.Ule'}"

Monday 18 February 2013

Enum ComboBox using MarkupExtension

         Quite often I find myself in need of using ComboBoxes together with Enum types instead of an ordinary items collection. More over those Enum values have to be translated using a .resx file to the current language. A complex and simple to use solution was needed. In this example I'll show how to create one using MarkupExtension.
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
  1. public class EnumValuesExtension : MarkupExtension
  2. {
  3.     private Type _enumType;
  4.     private String _resourceName;    
  5.     private bool _addEmptyValue = false;
  6.     // Enumeration type
  7.     public Type EnumType
  8.     {
  9.         set { _enumType = value; }
  10.     }
  11.     // Name of the class of the .resx file        
  12.     public String ResourceName
  13.     {
  14.         set { _resourceName = value; }
  15.     }    
  16.     // Add empty value flag        
  17.     public Boolean AddEmptyValue
  18.     {
  19.         set { _addEmptyValue = value; }
  20.     }
  21.     public EnumValuesExtension()
  22.     {
  23.     }

       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
  1. public override object ProvideValue(IServiceProvider serviceProvider)
  2. {
  3.     // Enumeration type not passed through XAML
  4.     if (_enumType == null)                        
  5.         throw new ArgumentNullException("EnumType (Property not set)");
  6.     if (!_enumType.IsEnum)                                  
  7.         throw new ArgumentNullException("Property EnumType must be an enum");
  8.     // Bindable properties list
  9.     List<dynamic> list = new List<dynamic>();        
  10.  
  11.     if (!String.IsNullOrEmpty(_resourceName))
  12.     {
  13.         // Name of the resource class
  14.         Type type = Type.GetType(_resourceName);  
  15.         if (type == null)                
  16.             throw new ArgumentException(
  17.                 "Resource name should be a fully qualified name");
  18.         // We iterate through the Enum values
  19.         foreach (var enumName in Enum.GetNames(_enumType))  
  20.         {
  21.             string translation = string.Empty;
  22.             var property = type.GetProperty(enumName,
  23.                 BindingFlags.Static |
  24.                 BindingFlags.Public |
  25.                 BindingFlags.NonPublic);
  26.             // If there's not translation for specific Enum value,
  27.             // there'll be a message shown instead
  28.             if (property == null)                 
  29.                 translation = String.Format(
  30.                     "Field {0} not found in the resource file {1}",
  31.                     enumName,
  32.                     _resourceName);
  33.             else
  34.                 translation = property.GetValue(null, null).ToString();
  35.                                                  
  36.             list.Add(GetNamed(translation, enumName));                    
  37.         }
  38.         // Adding empty row
  39.         if (_addEmptyValue)                       
  40.             list.Add(GetEmpty());
  41.         return list;
  42.     }
  43.     // If there's no resource provided Enum values will be used
  44.     // without translation
  45.     foreach (var enumName in Enum.GetNames(_enumType))  
  46.         list.Add(GetNamed(enumName, enumName));               
  47.  
  48.     if (_addEmptyValue)                                                  
  49.         list.Add(GetEmpty());
  50.  
  51.     return list;
  52. }

       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
  1. // Create one item which will fill our ComboBox ItemSource list
  2. private dynamic GetNamed(string translation, string enumName)
  3. {
  4.     // We create a bindable context
  5.     dynamic bindableResult = new ExpandoObject();    
  6.     // This dynamically created property will be
  7.     // bindable from XAML (through DisplayMemberPath or wherever)
  8.     bindableResult.Translation = translation;
  9.     // We're setting the value, which will be passed to SelectedItem
  10.     // of the ComboBox
  11.     bindableResult.Enum = enumName;                  
  12.     return bindableResult;
  13. }
  14.  
  15. // Create one empty item which will fill our ComboBox ItemSource list
  16. private dynamic GetEmpty()
  17. {
  18.     dynamic bindableResult = new ExpandoObject();
  19.     bindableResult.Translation = String.Empty;
  20.     bindableResult.Enum = null;
  21.     return bindableResult;
  22. }

       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
  1. <ComboBox Height="30" Width="100" VerticalAlignment="Top"
  2. SelectedValuePath="Enum" DisplayMemberPath="Translation"
  3. ItemsSource="{Binding Source={Extensions:ValuesEnum
  4. EnumType=Extensions:Types,
  5. ResourceName='Mod.Ule.Resources.Strings.Enums.Resource1,Mod.Ule'}}"
  6. SelectedValue="{Binding SelectedType}"/>