When working with Silverlight, there will be instances where you will require more than one converter to convert a binding item. As Silverlight supports only a single converter per binding object, what do we do? The solution? Simply create a converter to wrap all other converters in sequence, with the help of Attributes decorations, so that it will call each converter top-down when calling Convert, and bottom-up when calling ConvertBack.
Please note: The follow code is my slight modification (for Silverlight) from the article by Josh Smith located at http://www.codeproject.com/KB/WPF/PipingValueConverters_WPF.aspx which is the solution implemented for WPF. Please have a read through that link to get a better understanding of what this class should be doing
Create two classes:
The Attribute Class
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightClassLibrary1
{
/// <summary>
/// This attribute is used to decorate the Converters for use with
/// PipeConverter
/// so that more than one converters can be called at anyone time.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class PipedConverterAttribute : Attribute
{
// Fields
private Type _parameterType;
private Type _sourceType;
private Type _targetType;
// Methods
public PipedConverterAttribute(Type sourceType,
Type targetType)
{
if (sourceType == null)
{
throw new ArgumentNullException("sourceType");
}
if (targetType == null)
{
throw new ArgumentNullException("targetType");
}
this._sourceType = sourceType;
this._targetType = targetType;
}
public override int GetHashCode()
{
return (this._sourceType.GetHashCode() +
this._targetType.GetHashCode());
}
// Properties
public Type ParameterType
{
get
{
return this._parameterType;
}
set
{
this._parameterType = value;
}
}
public Type SourceType
{
get
{
return this._sourceType;
}
}
public Type TargetType
{
get
{
return this._targetType;
}
}
}
}
The Actual PipedConverter:
Use it as such in your XAML resource file: (converter is the namespace of the PipedConverter)using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Collections.Specialized;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace SilverlightClassLibrary1
{
/// <summary>
/// This is used to group a list of converters together,
/// calling them in sequence.
/// Eg. If you have three items, the targetType of the
/// firstItem must match the sourceType
/// of the secondItem and so forth.
/// </summary>
[System.Windows.Markup.ContentProperty("Converters")]
public class PipedConverter : IValueConverter
{
#region Backing Fields
private readonly ObservableCollection<IValueConverter>
_converters = new ObservableCollection<IValueConverter>();
private readonly Dictionary<IValueConverter,
PipedConverterAttribute> _cachedAttributes
= new Dictionary<IValueConverter,
PipedConverterAttribute>();
#endregion // Data
#region Properties (Converters)
/// <summary>
/// Returns the list of IValueConverters contained
/// in this converter.
/// </summary>
public ObservableCollection<IValueConverter> Converters
{
get { return this._converters; }
}
#endregion // Converters
#region Constructor
public PipedConverter()
{
this._converters.CollectionChanged +=
this.OnConvertersCollectionChanged;
}
#endregion // Constructor
#region IValueConverter Members
object IValueConverter.Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
object output = value;
for (int i = 0; i < this.Converters.Count; ++i)
{
IValueConverter converter = this.Converters[i];
Type currentTargetType =
this.GetTargetType(i, targetType, true);
output = converter.Convert(output,
currentTargetType, parameter, culture);
// If the converter didnt convert as expected then
// the binding operation should terminate.
if (output == DependencyProperty.UnsetValue)
break;
}
return output;
}
object IValueConverter.ConvertBack(object value,
Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
object output = value;
for (int i = this.Converters.Count - 1; i > -1; --i)
{
IValueConverter converter = this.Converters[i];
Type currentTargetType = this.GetTargetType(i,
targetType, false);
output = converter.ConvertBack(output,
currentTargetType, parameter, culture);
// If the converter didn’t convert as expected
// then the binding operation should terminate.
if (output == DependencyProperty.UnsetValue)
break;
}
return output;
}
#endregion
#region GetTargetType
/// <summary>
/// Returns the target type for a conversion operation.
/// </summary>
/// <param name="converterIndex">The index of the
/// current converter about to be executed.</param>
/// <param name="finalTargetType">The 'targetType'
/// argument passed into the conversion method.</param>
/// <param name="convert">Pass true if calling from the Convert
/// method, or false if calling from ConvertBack.</param>
protected virtual Type GetTargetType(int converterIndex,
Type finalTargetType, bool convert)
{
// If the current converter is not the last/first in the
// list, get a reference to the next/previous converter.
IValueConverter nextConverter = null;
if (convert) // Always forward until last item
{
if (converterIndex < this.Converters.Count - 1)
{
nextConverter =
this.Converters[converterIndex + 1];
if (nextConverter == null)
throw new InvalidOperationException();
}
}
else // Always going backwards until first item
{
if (converterIndex > 0)
{
nextConverter =
this.Converters[converterIndex - 1];
if (nextConverter == null)
throw new InvalidOperationException();
}
}
if (nextConverter != null)
{
// Get the attributes of the next converter
// and return the type
PipedConverterAttribute conversionAttribute =
_cachedAttributes[nextConverter];
// If the Convert method is going to be called,
// we need to use the SourceType of the next
// converter in the list. If ConvertBack is called,
// use the TargetType.
return convert ? conversionAttribute.SourceType :
conversionAttribute.TargetType;
}
// If the current converter is the last one to be executed
// return the target type passed into the conversion method.
return finalTargetType;
}
#endregion // GetTargetType
#region OnConvertersCollectionChanged
void OnConvertersCollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
// The 'Converters' collection has been modified,
// so validate that each value converter it now
// contains is decorated with ConverterAttribute and
// then cache the attribute value.
IList convertersToProcess = null;
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
convertersToProcess = e.NewItems;
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (IValueConverter converter in e.OldItems)
this._cachedAttributes.Remove(converter);
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
this._cachedAttributes.Clear();
convertersToProcess = this._converters;
}
if (convertersToProcess != null
&& convertersToProcess.Count > 0)
{
foreach (IValueConverter converter in convertersToProcess)
{
object[] attributes = converter.GetType()
.GetCustomAttributes(
typeof(PipedConverterAttribute), false);
if (attributes.Length != 1)
throw new InvalidOperationException();
this._cachedAttributes.Add(converter,
attributes[0]
as PipedConverterAttribute);
}
}
}
#endregion
}
}
<converter:PipedConverter x:Key="pipedConverter">
<converter:ConverterA />
<converter:ConverterB />
</converter:PipedConverter >
Also do decorate your converters like so:
[PipedConverterAttribute(typeof(string), typeof(int))]
public class ConverterA : IValueConverter
Where string is the input type, and integer is the output type of the converter.
No comments:
Post a Comment