With converters

Converters are yet another way that we can package up useful functionality in our framework. We've already seen a useful example of the IValueConverter interface in Chapter 2, Debugging WPF Applications, but while that was a very simple example, converters can actually be very versatile.

Long before Microsoft introduced their BooleanToVisibilityConverter class, developers had to create their own versions. We often need to convert the UIElement.Visibility enumeration to or from a variety of different types, and so it is a good idea to start with a BaseVisibilityConverter class that can serve multiple converter classes. Let's see what that entails:

using System.Windows; 
using System.Windows.Data; 
 
namespace CompanyName.ApplicationName.Converters 
{ 
  public abstract class BaseVisibilityConverter 
  { 
    public enum FalseVisibility { Hidden, Collapsed } 
 
    protected Visibility FalseVisibilityValue { get; set; } = 
      Visibility.Collapsed; 
 
    public FalseVisibility FalseVisibilityState 
    { 
      get { return FalseVisibilityState == Visibility.Collapsed ?              
        FalseVisibility.Collapsed : FalseVisibility.Hidden; }
      set { FalseVisibilityState = value == FalseVisibility.Collapsed ?
        Visibility.Collapsed : Visibility.Hidden; } 
    } 
 
    public bool IsInverted { get; set; } 
  } 
} 

This converter requires one value to represent the visible value and as there is only one corresponding value in the UIElement.Visibility enumeration, that will clearly be the Visibility.Visible instance. It also requires a single value to represent the invisible value.

As such, we declare the FalseVisibility enumeration with the two corresponding values from the UIElement.Visibility enumeration and the FalseVisibilityValue property to enable users to specify which value should represent the false state. Note that the most commonly used Visibility.Collapsed value is set as the default value.

Users can set the FalseVisibilityState property when using the control and this sets the protected FalseVisibilityValue property internally. Finally, we see the indispensable IsInverted property that is optionally used to invert the result. Let's see what our BoolToVisibilityConverter class looks like now:

using System; 
using System.Globalization; 
using System.Windows; 
using System.Windows.Data; 
 
namespace CompanyName.ApplicationName.Converters 
{ 
  [ValueConversion(typeof(bool), typeof(Visibility))] 
  public class BoolToVisibilityConverter : BaseVisibilityConverter,  
    IValueConverter 
  { 
    public object Convert(object value, Type targetType,  
      object parameter, CultureInfo culture) 
    { 
      if (value == null || value.GetType() != typeof(bool))  
        return DependencyProperty.UnsetValue; 
      bool boolValue = IsInverted ? !(bool)value :(bool)value;
      return boolValue ? Visibility.Visible : FalseVisibilityValue; 
    } 
 
    public object ConvertBack(object value, Type targetType,  
      object parameter, CultureInfo culture) 
    { 
      if (value == null || value.GetType() != typeof(Visibility))  
        return DependencyProperty.UnsetValue; 
      if (IsInverted) return (Visibility)value != Visibility.Visible; 
      return (Visibility)value == Visibility.Visible; 
    } 
  } 
} 

We start by specifying the data types involved in the implementation of the converter in the ValueConversion attribute. This helps tools to know what types are being used in the converter, but also makes it clear to the users of our framework. Next, we extend our BaseVisibilityConverter base class and extend the required IValueConverter interface.

In the Convert method, we first check the validity of our value input parameter, if valid, we convert it to a bool variable, taking the IsInverted property setting into consideration. We return the DependencyProperty.UnsetValue value for invalid input values. Finally, we resolve the output value from this bool variable to either the Visibility.Visible instance, or the value of the FalseVisibilityValue property.

In the ConvertBack method, we also check the validity of our value input parameter first. We return the DependencyProperty.UnsetValue value for invalid input values again, otherwise we output a bool value that specifies whether the input parameter of type Visibility is equal to the Visibility.Visible instance, while again taking the value of the IsInverted property into consideration.

Note that use of the IsInverted property enables users to specify that elements should become visible when the data bound bool value is false. This can be incredibly useful when we want to have one object visible upon a certain condition and another object hidden dependent upon the same condition. We can declare two converters from this class like this:

xmlns:Converters="clr-namespace:CompanyName.ApplicationName.Converters; 
  assembly=CompanyName.ApplicationName.Converters" 
... 
<Converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" /> 
<Converters:BoolToVisibilityConverter  
  x:Key="InvertedBoolToVisibilityConverter" IsInverted="True" /> 

As stated, we often need to convert to and from the UIElement.Visibility enumeration from a variety of different types. Let's now look at an example of a conversion to and from the Enum type. The principle is the same as the last example, where a single data bound value represents the Visibility.Visible instance and all other values represent the hidden or collapsed state:

using System; 
using System.Globalization; 
using System.Windows; 
using System.Windows.Data; 
 
namespace CompanyName.ApplicationName.Converters 
{ 
  [ValueConversion(typeof(Enum), typeof(Visibility))] 
  public class EnumToVisibilityConverter : BaseVisibilityConverter,  
    IValueConverter
  { 
    public object Convert(object value, Type targetType,  
      object parameter, CultureInfo culture) 
    { 
      if (value == null || (value.GetType() != typeof(Enum) &&     
        value.GetType().BaseType != typeof(Enum)) ||  
        parameter == null) return DependencyProperty.UnsetValue; 
      string enumValue = value.ToString(); 
      string targetValue = parameter.ToString(); 
      bool boolValue = enumValue.Equals(targetValue,  
        StringComparison.InvariantCultureIgnoreCase); 
      boolValue = IsInverted ? !boolValue : boolValue; 
      return boolValue ? Visibility.Visible : FalseVisibilityValue; 
    } 
 
    public object ConvertBack(object value, Type targetType,  
      object parameter, CultureInfo culture) 
    { 
      if (value == null || value.GetType() != typeof(Visibility) ||
        parameter == null) return DependencyProperty.UnsetValue; 
      Visibility usedValue = (Visibility)value; 
      string targetValue = parameter.ToString(); 
      if (IsInverted && usedValue != Visibility.Visible)  
        return Enum.Parse(targetType, targetValue); 
      else if (!IsInverted && usedValue == Visibility.Visible)  
        return Enum.Parse(targetType, targetValue); 
      return DependencyProperty.UnsetValue; 
    } 
  } 
} 

Again, we start by specifying the data types involved in the implementation of the converter in the ValueConversion attribute. In the Convert method, we first check the validity of our value input parameter, if valid, we convert it to the string representation of the value. This particular class uses the parameter input parameter to pass the specified enumeration instance that will represent the visible value, and so it is set to the targetValue variable as a string.

We then create a bool value by comparing the current enumeration instance with the target instance. Once we have our bool value, the last two lines replicate those in the BoolToVisibilityConverter class.

The ConvertBack method implementation is somewhat different. Logically speaking, we are unable to return the correct enumeration instance for a hidden visibility, as it could be any value except the visible value passed through the parameter input parameter.

As such, we are only able to return that specified value if the element is visible and the IsInverted property is false, or if it is not visible and the IsInverted property is true. For all other input values, we simply return the DependencyProperty.UnsetValue property to state that there is no value.

Another incredibly useful thing that converters can do is to convert individual enumeration instances to particular images. Let's look at an example that relates to our FeedbackManager, or, more accurately, the Feedback objects that get displayed. Each Feedback object can have a particular type that is specified by the FeedbackType enumeration, so let's look at that first:

namespace CompanyName.ApplicationName.DataModels.Enums 
{ 
  public enum FeedbackType 
  { 
    None = -1, 
    Error, 
    Information, 
    Question, 
    Success, 
    Validation, 
    Warning 
  } 
} 

To make this work, we obviously need a suitable image for each enumeration instance, except for the None instance. Our images will reside in a folder named Images in the root folder of the startup project:

using CompanyName.ApplicationName.DataModels.Enums;
using System; using System.Globalization; using System.Windows; using System.Windows.Data; using System.Windows.Media; namespace CompanyName.ApplicationName.Converters { [ValueConversion(typeof(FeedbackType), typeof(ImageSource))] public class FeedbackTypeToImageSourceConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (!(value is FeedbackType feedbackType) ||
targetType != typeof(ImageSource))
return DependencyProperty.UnsetValue; string imageName = string.Empty; switch ((FeedbackType)value) { case FeedbackType.None: return null; case FeedbackType.Error: imageName = "Error_16"; break; case FeedbackType.Success: imageName = "Success_16"; break; case FeedbackType.Validation: case FeedbackType.Warning: imageName = "Warning_16"; break; case FeedbackType.Information: imageName = "Information_16"; break; case FeedbackType.Question: imageName = "Question_16"; break; default: return DependencyProperty.UnsetValue; } return $"pack://application:,,,/CompanyName.ApplicationName; component/Images/{ imageName }.png"; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return DependencyProperty.UnsetValue; } } }

Once again, we start by specifying the data types involved in the converter in the ValueConversion attribute. In the Convert method, we use C# 6.0 Pattern Matching to check the validity of our value input parameter and to cast it to a FeedbackType instance, if valid. We then use that in a switch statement, to generate the relevant image name for each enumeration instance.

If an unknown instance is used, we return the DependencyProperty.UnsetValue value. In all other cases, we use String Interpolation to build up the full file path of the relevant image and then return it from the converter as the converted value. As the ConvertBack method in this converter has no valid use, it is not implemented and simply returns the DependencyProperty.UnsetValue value.

You may have noticed that we specified type ImageSource in the ValueConversion attribute, but we returned a string. This is possible because XAML uses the relevant type converter to convert the string into an ImageSource object automatically for us. Exactly the same thing occurs when we set an Image.Source property with a string in XAML.

As with other parts of our framework, we can make our converters even more useful, when we combine functionality from other areas. In this particular example, we utilize one of the Extension Methods that was shown earlier in this chapter. To remind you, the GetDescription method will return the value of the DescriptionAttribute that is set on each enumeration instance.

The DescriptionAttribute enables us to associate any string value with each of our enumeration instances, so this is a great way to output a user-friendly description for each instance. An example of this would be as follows:

using System.ComponentModel;

public enum BitRate { [Description("16 bits")] Sixteen = 16, [Description("24 bits")] TwentyFour = 24, [Description("32 bits")] ThirtyTwo = 32, }

In this way, instead of displaying the names of the instances in a RadioButton control, for example, we could display the more humanized descriptions from these attributes. Let's have a look at this converter class now:

using System; 
using System.Globalization; 
using System.Windows; 
using System.Windows.Data; 
using CompanyName.ApplicationName.Extensions; 
 
namespace CompanyName.ApplicationName.Converters 
{ 
  [ValueConversion(typeof(Enum), typeof(string))] 
  public class EnumToDescriptionStringConverter : IValueConverter 
  { 
    public object Convert(object value, Type targetType,  
      object parameter, CultureInfo culture) 
    { 
      if (value == null || (value.GetType() != typeof(Enum) &&  
        value.GetType().BaseType != typeof(Enum)))  
        return DependencyProperty.UnsetValue; 
      Enum enumInstance = (Enum)value; 
      return enumInstance.GetDescription(); 
    } 
 
    public object ConvertBack(object value, Type targetType,  
      object parameter, CultureInfo culture) 
    { 
      return DependencyProperty.UnsetValue; 
    } 
  } 
} 

As we're now accustomed to doing, we start by specifying the data types used in the converter in the ValueConversion attribute. In the Convert method, we again check the validity of our value input parameter and return the DependencyProperty.UnsetValue value if it is invalid.

If it is valid, we cast it to a Enum instance and then use the power of our Extension Method to return the value from each instance's DescriptionAttribute. In doing so, we are able to expose this functionality to our Views and to enable the users of our framework to utilize it directly from the XAML. Now that we have a general understanding of the various ways that we can encapsulate functionality into our framework, let's focus on starting construction of our base classes.