While working on a Silverlight business application, I was startled at the proliferation of value converters within my project. It started out innocent enough. One class named BooleanToVisibilityConverter, and its cousin BooleanInverseToVisibilityConverter. Eventually, there were IValueConverters implementations for entirely non-reuable situations, such as MyEntityCollectionToBrushBasedOnSomeDetailedCriteraConverter.
That's when this idea struck me. To avoid the need to manage a limitless library of value converters, why not create an event-driven value converter that delegates the conversion to a listener, such as a user control or page. Implementing the solution turned out to be fairly simple.
First, I created an EventArgs derivative to carry the converter's method arguments and collect the result, like so:
public class ValueConvertingEventArgs : EventArgs{ public ValueConvertingEventArgs( object value, Type targetType, object parameter, CultureInfo culture) { this.Value = value; this.TargetType = targetType; this.Parameter = parameter; this.Culture = culture; } public object Value { get; private set; } public Type TargetType { get; private set; } public object Parameter { get; private set; } public CultureInfo Culture { get; private set; } public object Result { get; set; }}
Next, I implemented an IValueConverter that "forwards" the Convert and ConvertBack handling to event listeners, as shown below:
public class DelegatedValueConverter : IValueConverter{ public event EventHandler<ValueConvertingEventArgs> Converting; public event EventHandler<ValueConvertingEventArgs> ConvertingBack; public object Convert( object value, Type targetType, object parameter, CultureInfo culture) { if (DesignerProperties.IsInDesignTool) return null; var handler = this.Converting; if (handler == null) throw new InvalidOperationException( "Subscription to 'Converting' is required."); var args = new ValueConvertingEventArgs( value, targetType, parameter, culture); handler(this, args); return args.Result; } public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture) { if (DesignerProperties.IsInDesignTool) return null; var handler = this.ConvertingBack; if (handler == null) throw new InvalidOperationException( "Subscription to 'ConvertingBack' is required."); var args = new ValueConvertingEventArgs( value, targetType, parameter, culture); handler(this, args); return args.Result; }}
public class DelegatedValueConverter : IValueConverter{ public event EventHandler<ValueConvertingEventArgs> Converting; public event EventHandler<ValueConvertingEventArgs> ConvertingBack; public object Convert( object value, Type targetType, object parameter, CultureInfo culture) { if (DesignerProperties.IsInDesignTool)
return null;
var handler = this.Converting; if (handler == null) throw new InvalidOperationException( "Subscription to 'Converting' is required."); var args = new ValueConvertingEventArgs( value, targetType, parameter, culture); handler(this, args); return args.Result; } public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture) { if (DesignerProperties.IsInDesignTool)
var handler = this.ConvertingBack; if (handler == null) throw new InvalidOperationException( "Subscription to 'ConvertingBack' is required."); var args = new ValueConvertingEventArgs( value, targetType, parameter, culture); handler(this, args); return args.Result; }}
This implementation throws an InvalidOperationException if the related event does not have any subscribers. After the event executes, the method returns the value of the Result property on the event arguments. The snippets below show the value converter in action:
XAML:
<navigation:Page.Resources> <local:DelegatedValueConverter x:Key="DelegatedConverter" Converting="OnConverting" /></navigation:Page.Resources>...<TextBlock Text={Binding Stuff, Converter={StaticResource DelegatedConverter}} />
C# Code-Behind:
private void OnConverting(object sender, ValueConvertingEventArgs e){ e.Result = string.Format(e.Culture, "Hello, {0}", e.Value);}
With the DelegatedValueConverter in tow, I was able to reduce the overall number of IValueConverter implementations in my project. The new converter allowed the page to take responsibility for conversions each time it seemed appropriate. I still make new value converter classes if they seem to have general reusability; however, for one-offs or page-specific conversions, this approach meets the conversion need without flooding the assembly with converter classes.
Happy Coding!