Home   Subscribe   Linkedin
  Archive Contact  

Multilevel Converter in WPF

Converters basically provides a translation between your binding source and destination.

Generally we use single level converters like (Bool to Visibility Converter), (Color to Brush Converter), (Enum to Bool converter) and list is endless..

Now consider a scenario in which we require Multi-level conversion i.e from A => B => C.

Lets take an example, Suppose we have Bool to Visibility Converter and Color to Bool converter, and we want a translation like Color to Visibility Conversion.

So instead of making Color To Visibility converter we can make some generic converter that allows us to do multilevel Conversion.

Below is the Code of that Multi level Converter

public class ValueConverterGroup : IValueConverter
  {
    #region Data
    /// <summary>
    /// Stores the list of values, on which the converter has to be applied.
    /// </summary>
    private readonly ObservableCollection<IValueConverter> converters = new ObservableCollection<IValueConverter>();

    /// <summary>
    /// Stores the dictionary, mapping of value with its conversion attribute.
    /// </summary>
    private readonly Dictionary<IValueConverter, ValueConversionAttribute> cachedAttributes = new Dictionary<IValueConverter, ValueConversionAttribute>();

    #endregion

    #region Constructor
    /// <summary>
    /// 
    /// </summary>
    public ValueConverterGroup()
    {
      this.converters.CollectionChanged += this.OnConvertersCollectionChanged;
    }

    #endregion

    #region Converters

    /// <summary>
    /// Returns the list of IValueConverters contained in this converter.
    /// </summary>
    public ObservableCollection<IValueConverter> Converters
    {
      get { return this.converters; }
    }

    #endregion

    #region IValueConverter Members

    object IValueConverter.Convert(object value, Type targetType, object parameter, 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 returns 'DoNothing' then the binding operation should terminate.
        if (output == Binding.DoNothing)
          break;
      }

      return output;
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, 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);

        // When a converter returns 'DoNothing' the binding operation should terminate.
        if (output == Binding.DoNothing)
          break;
      }

      return output;
    }

    #endregion

    #region Private Helpers

    #region GetTargetType

    /// <summary>
    /// Returns the target type for a conversion operation.
    /// </summary>
    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)
      {
        if (converterIndex < this.Converters.Count - 1)
        {
          nextConverter = this.Converters[converterIndex + 1];
          if (nextConverter == null)
            throw new InvalidOperationException("The Converters collection of the ValueConverterGroup contains a null reference at index: " + (converterIndex + 1));
        }
      }
      else
      {
        if (converterIndex > 0)
        {
          nextConverter = this.Converters[converterIndex - 1];
          if (nextConverter == null)
            throw new InvalidOperationException("The Converters collection of the ValueConverterGroup contains a null reference at index: " + (converterIndex - 1));
        }
      }

      if (nextConverter != null)
      {
        ValueConversionAttribute 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 ValueConversionAttribute 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(ValueConversionAttribute), false);

          if (attributes.Length != 1)
            throw new InvalidOperationException("All value converters added to a ValueConverterGroup must be decorated with the ValueConversionAttribute attribute exactly once.");

          this.cachedAttributes.Add(converter, attributes[0] as ValueConversionAttribute);
        }
      }
    }
    #endregion
    #endregion
  }

And in Xaml simply define your Converters in a required order, like

<convert:ValueConverterGroup x:Key="colorToVisibilityConverter">
        <convert:ColorToBooleanConverter />
        <convert:BooleanToVisibilityConverter />
      </convert:ValueConverterGroup>

 and thats all. You can have n levels of conversion A => B => C => D.....N levels

Posted by: Mohd Ahmed

Categories: C#, Prism, Silverlight, WPF, Xaml

Tags: , , , ,

Silverlight Init Parameters with MEF, Prism and a Myth

This Article describes How to pass Init Parameters in different Silverlight xap files when using a Modular approach

Generally when we want to pass Init parameters into Silverlight object at runtime, we follow something like this.

//App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = new MainPage(e.InitParams);
}
 
//MainPage.xaml.cs
public partial class MainPage : UserControl
{
    public MainPage(IDictionary<string, string> initParams)
    {
        var myValue = initParams["MyKey"];
        InitializeComponent();
    }
}
 
TestPage.aspx
 
    <form id="form1" runat="server" style="height: 100%">
    <div id="silverlightControlHost">
        <object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
            width="100%" height="100%">
            <param name="source" value="ClientBin/SilverlightApp.xap" />
            <param name="onError" value="onSilverlightError" />
            <param name="background" value="white" />
            <param name="minRuntimeVersion" value="4.0.50826.0" />
            <param name="autoUpgrade" value="true" />
            <param name="initParams" value="MyKey=Effectlabs" />
            <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0" style="text-decoration: none">
                <img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight"
                    style="border-style: none" />
            </a>
        </object>
        <iframe id="_sl_historyFrame" style="visibility: hidden; height: 0px; width: 0px;
            border: 0px"></iframe>
    </div>
    </form>

And it is the same piece of code which is found almost every where if you google on how to use Init Parameters with Silverlight.

The code seems to perfect in almost all the cases. However, it would need a reconsideration in few cases.

Imagine a situation when you are working with MEF and you want to pass the Init Parameters into the second xap file which is called by the primary xap file.

Seems to be tedious as:

There is no role of App.xaml.cs in the second ”Module”, meaning there is no startup object there, or in other words an application can get to use only one App.xaml.cs as a time.

In the App.xaml.cs of MEF based Project, we generally call new Bootstrapper() which then calls “Shell” whose constructor looks something like this

[ImportingConstructor]
public Shell([Import]IModuleManager modulemanager)
{
     InitializeComponent();

So one cannot manage to insert the above piece of code anywhere in the modular approach

Now the question is how we achieve the task of sending the Init Parameters to the second module if we don’t even have its access in first module.

In past few day while working with MEF, I observed that the resources inside “App.xaml.cs” of the Main Module are shared between all the other modules. Now there is something called “Application.Current.Host.InitParams” which I guessed must also have been shared along with the other resources, but in this approach, I couldn’t have set something inside it manually, and then I recollected that we didn’t even used to do it in the regular non modular approach, but something automatically gets into it when the parameters are passed

and the guess was right, I was able to access the Init Parameters by this way

   1:  public MainPage()
   2:  {
   3:      var initParam = Application.Current.Host.InitParams;
   4:      if(initParam.ContainsKey("MyKey")) InitParameterValue = initParam["MyKey"];
   5:      InitializeComponent();
   6:      DataContext = this;
   7:  }

image_thumb1_thumb

image_thumb

image_thumb3_thumb

Hence making the things pretty simpler, we finally have two questions answered:

1) How to pass Init Parameters to other modules of MEF based project ?

Passing Init Parameters from one module to other is not exactly a task, but it’s a matter of accessing it.

2) is there any other method of passing the Init Parameters then the regular traditional one ?

It’s a Myth that the method demonstrated above (very top) or on almost every site to pass Init Parameters is the only way to do it, rather the constructor of Root Visual does not necessarily need to accept an I-Dictionary in order to prove it accepts Init Parameters.

I have attached the sample project used in this demonstration.

Posted by: Zeeshan

Categories: Prism, MEF, Silverlight

Tags: , , , ,