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: , , , ,

How to Bind Data to a Property in Silverlight

In this article i am going to describe how to Bind Data to a Property in Silverlight in some simple steps.

Step 1: Create a new Silverlight Application Project, give it a name say BindingInSilverlight.

Step 2: Go to MainPage.xaml and prepare UI.

Initially xaml inside it will look like this:

<UserControl x:Class="BindingInSilverlight.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="800">

    <Grid x:Name="LayoutRoot" Background="White" Margin="50,20,0,0">

    </Grid>
</UserControl>

Now add RowDefinitions and ColumnDefinitions in Grid tag,

it will give a better look and feel to our layout.

<Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="150"/>
        </Grid.ColumnDefinitions>

Add some Controls in it like TextBlock and TextBox with Grid.Row and Grid.Column specifications for accurate layout.

Add Binding paraeters: Name and Place are two Properties to Bind here.

<TextBlock Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="0" Text="Insert Values to Bind"
 FontSize="16" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        <TextBlock Grid.Row="1" FontSize="14" Grid.Column="0" Text="Name :" VerticalAlignment="Center"/>
        <TextBlock Grid.Row="2" FontSize="14" Grid.Column="0" Text="Place To Visit:" VerticalAlignment="Center"/>
        <TextBox FontSize="14" Text="{Binding Name, Mode=TwoWay}" Grid.Row="1" Grid.Column="1" Margin="5"/>
        <TextBox FontSize="14" Text="{Binding Place, Mode=TwoWay}" Grid.Row="2" Grid.Column="1" Margin="5"/>
        
        <TextBlock Grid.ColumnSpan="2" Grid.Column="3" Grid.Row="0" Text="Binding..." FontSize="16" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        <TextBlock Grid.Row="1" FontSize="14" Grid.Column="3" Text="Hello:" VerticalAlignment="Center"/>
        <TextBlock Grid.Row="2" FontSize="14" Grid.Column="3" Text="Welcome to:" VerticalAlignment="Center"/>
        <TextBlock FontSize="14" Text="{Binding Name}" Grid.Row="1" Grid.Column="4" Margin="5"/>
        <TextBlock FontSize="14" Text="{Binding Place}" Grid.Row="2" Grid.Column="4" Margin="5"/>

Finally our UI will look like this:

Left Side to get Data from User, and Right side to show binding.

 

Step 3: Go to MainPage.xaml.cs page, now we have to add some Code there.

First of all implement INotifyPropertyChanged interface with namespace System.ComponentModel,

the INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.

Also define Properties Name and Place with their get and set methods along with a OnPropertyChanged method,

this will be called when it notifies some change in the Property.

After doing all our MainPage.xaml.cs will look something like this:

using System.ComponentModel;

namespace BindingInSilverlight
{
    public partial class MainPage : INotifyPropertyChanged                         // INotifyPropertyChanged interface.
    {
        public event PropertyChangedEventHandler PropertyChanged;                  // PropertyChanged event 

        private void OnPropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(property));            // Notification.
            }
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        private string _place;
        public string Place
        {
            get { return _place; }
            set
            {
                _place = value;
                OnPropertyChanged("Place");
            }
        }

        public MainPage()
        {
            DataContext = this;
            InitializeComponent();
        }
    }
}

Step 4: Start filling Details.

you will see whenever you navigate away from any TextBox its contents are automatically

visible in the Right hand side of the Layout as:

This is how we can Bind Data to a Property in Silverlight.

Posted by: Arun Verma

Categories: Silverlight

Tags: ,