Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CLRBinding - Properties Binding

0.00/5 (No votes)
23 Jun 2012 1  
Binding Properties of non GUI elements that still implement INotify

Introduction

During a system development and its implementation it became a need to Bind some properties of a class representing an Entity from a DB to a wrapper class used as a View Model.

One way to accomplish it was to add a Handler to a PropertyChanged event and in the handler compare the name of that property; if the name is the one looked for then to do something, of course some cheap work around can be made to update the source property in case the target was changed, but this is simpler.

Any how, let's consider that the source class has lots of properties. In such a case the PropertyChnaged event may be fired quite frequently and in the handler a switch case statement or many if statements will be needed to filter the handler for the corresponding property and it is not such a pretty implementation.

The wrong way, in my opinion, to do it presented in the next code snippet

private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch(e.PropertyName)
    {
        case "property1":
           {
               //do something
               break;
           }
        case "property2":
           {
               //do something
               break;
           }
       //and so on ...
    }
}

In this article presented a mechanism for binding properties selectively and similar to the way implemented by the WPF. in WPF for example almost each GUI element has a Method SetBinding(DependencyProperty, Binding). but a Class that implements interface such as INotifyPropertyChanged but not derived from some GUI class such as those in Windows Presentation Framework (WPF) are lacking of such mechanism as SetBinding(DependencyProperty, Binding) that enables binding of properties.

Background

Each class that wants to notify all that interested in the fact that one of it's properties has changed has to implement interface such as INotifyPropertyChanged. The standard implementation of such an Interface is as follows:

#region INotifyPropertyChanged Members
 
public event PropertyChangedEventHandler PropertyChanged;
 
public void OnPropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}
 
#endregion 

Two most common ways using the implementation of INotifyPropertyChanged in the code are:

private string m_Name;
 
public string Name
{
    get { return m_Name; }
    set
    {
        m_Name=value;
        if (OnPropertyChanged!=null)
        {
            OnPropertyCanged("Name");
        }
    }
}
 
// or

public string Name
{
    get { return m_Name; }
    set
    {
        if (m_Name!=value)
        {
            m_Name=value;
            if (OnPropertyChanged!=null)
            {
                OnPropertyCanged("Name");
            }
        }
    }
} 

Typical way to sign for notifications of property change is:

OnPropertyChanged += new PropertyChangedEventHandler(PropertyChanged);

By invoking the event OnPropertyCanged("Name") every handler signed for the event OnPropertyChanged will be notified.

So far what was described are the mechanics to create and use properties, to understand more deeply the mechanics of Properties I suggest to start here and just travel the links.

In the WPF there is already built in mechanics for properties binding either through Xaml or in the code behind, and for it some parameters of binding needed to be defined such as Binding Mode (Two Way, One Way To Source, One Way To Target, etc.), the source and the target objects for binding and their properties to be bounded, the update trigger and etc. but as mentioned before that method is mechanism is accessible only for the GUI elements and controls, but what if there is a requirement to bind properties of not GUI elements and controls?

Code and Answer

Well as an answer for previously asked question next code is provided:

namespace CLRBinding
{
    public class CLRBinding
    {
        List<clrpropertiesbinding> Bindings = new List<clrpropertiesbinding>();
 
        public void Add(CLRPropertiesBinding binding)
        {
            Bindings.Add(binding);
        }
 
        public void Remove(CLRPropertiesBinding binding)
        {
            Bindings.Remove(binding);
            binding.Unbind();
        }
 
        public void Bind()
        {
            foreach (var binding in Bindings)
            {
                binding.Bind();
            }
        }
    }
}

namespace CLRBinding
{
    public enum CLRBindingMode
    {
        OneWayToTarget,
        OneWayToSource,
        TwoWay
    }
 
    public class CLRPropertiesBinding
    {
        #region Memebers
 
        private readonly CLRBindingMode m_Mode;
        private readonly object m_Source;
        private readonly object m_Target;
        private readonly PropertyDescriptor m_SourceProperty;
        private readonly PropertyDescriptor m_TargetProperty;
        private readonly IValueConverter m_Converter;
 
        #endregion
 
        #region Constructors
 
        public CLRPropertiesBinding(object source, string sourceProperty, object target, 
               string targetProperty, CLRBindingMode mode, IValueConverter converter )
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
 
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }
 
            PropertyDescriptorCollection sourceProperties = TypeDescriptor.GetProperties(source);
            
            m_SourceProperty = sourceProperties.Find(sourceProperty, false);
            
            if (m_SourceProperty == null)
            {
                throw new ArgumentException("Invalid source property");
            }
            
            if ((mode == CLRBindingMode.TwoWay || mode == CLRBindingMode.OneWayToSource) && m_SourceProperty.IsReadOnly)
            {
                throw new ArgumentException("TwoWay and OneWayToSource are not supported on a readonly source property");
            }
 
            PropertyDescriptorCollection targetProperties = TypeDescriptor.GetProperties(target);
           
            m_TargetProperty = targetProperties.Find(targetProperty, false);
            
            if (m_TargetProperty == null)
            {
                throw new ArgumentException("Invalid target property");
            }
            
            if (mode != CLRBindingMode.OneWayToSource && m_TargetProperty.IsReadOnly)
            {
                throw new ArgumentException("TwoWay and OneWayToTarget are " + 
                      "not supported on a readonly target property");
            }
            m_Source = source;
            m_Target = target;
            m_Mode = mode;
            m_Converter = converter;
        }
        public CLRPropertiesBinding(object source, string sourceProperty, object target, 
               string targetProperty, IValueConverter converter = null) : 
               this(source, sourceProperty, target, targetProperty, CLRBindingMode.TwoWay, converter) { }
        public CLRPropertiesBinding(object source, string sourceProperty, object target, 
               string targetProperty, CLRBindingMode mode = CLRBindingMode.TwoWay) : 
               this(source, sourceProperty, target, targetProperty, CLRBindingMode.TwoWay, null) { }
        public CLRPropertiesBinding(object source, string sourceProperty, object target, 
               string targetProperty) : this(source, sourceProperty, target, 
               targetProperty, CLRBindingMode.TwoWay, null) { }
        public CLRPropertiesBinding(object source, object target, string property, 
               CLRBindingMode mode, IValueConverter converter) : this(source, property, 
               target, property, mode, converter) { } 
        public CLRPropertiesBinding(object source, object target, string property, 
               CLRBindingMode mode) : this(source, target, property, mode, null) { }
        public CLRPropertiesBinding(object source, object target, string property, 
               IValueConverter converter) : this(source, target, property, CLRBindingMode.TwoWay, converter) { }
        public CLRPropertiesBinding(object source, object target, string property) : 
               this(source, target, property, CLRBindingMode.TwoWay, null) { }
 
        #endregion
 
        #region Operations
 
        internal void Bind()
        {
            if (m_Mode == CLRBindingMode.OneWayToTarget || m_Mode == CLRBindingMode.TwoWay)
            {
                m_SourceProperty.AddValueChanged(m_Source, SourcePropertyChanged);
            }
 
            if (m_Mode == CLRBindingMode.OneWayToSource || m_Mode == CLRBindingMode.TwoWay)
            {
                m_TargetProperty.AddValueChanged(m_Target, TargetPropertyChanged);
            }
            UpdateTarget();
        }
 
        internal void Unbind()
        {
            if (m_Mode == CLRBindingMode.OneWayToTarget || m_Mode == CLRBindingMode.TwoWay)
            {
                m_SourceProperty.RemoveValueChanged(m_Source, SourcePropertyChanged);
            }
 
            if (m_Mode == CLRBindingMode.OneWayToSource || m_Mode == CLRBindingMode.TwoWay)
            {
                m_TargetProperty.RemoveValueChanged(m_Target, TargetPropertyChanged);
            }
        }
 
        #endregion
 
        #region Privates
 
        private void SourcePropertyChanged(object sender, EventArgs e)
        {
            UpdateTarget();            
        }
 
        private void UpdateTarget()
        {
            object value = m_SourceProperty.GetValue(m_Source);
            m_TargetProperty.RemoveValueChanged(m_Target, TargetPropertyChanged);
            if (m_Converter != null)
            {
                value = m_Converter.Convert(value, m_TargetProperty.GetType(), null, CultureInfo.CurrentCulture);
            }
            m_TargetProperty.SetValue(m_Target, value);
            m_TargetProperty.AddValueChanged(m_Target, TargetPropertyChanged);
        }
 
        private void TargetPropertyChanged(object sender, EventArgs e)
        {
            UpdateSource();            
        }
 
        private void UpdateSource()
        {
            object value = m_TargetProperty.GetValue(m_Target);
            m_SourceProperty.RemoveValueChanged(m_Source, SourcePropertyChanged);
            if (m_Converter != null)
            {
                value = m_Converter.ConvertBack(value, m_TargetProperty.GetType(), null, CultureInfo.CurrentCulture);
            }
            m_SourceProperty.SetValue(m_Source, value);
            m_SourceProperty.AddValueChanged(m_Source, SourcePropertyChanged);
        }
 
        #endregion        
 
    }
}

This code enables binding properties of classes that implement interfaces such as INotifyPropertyChanged and not necessary a derivation of some GUI element or Controls.

The solution consists of two classes: CLRBinding and CLRPropertiesBinding. and enum CLRBindingMode.

CLRBindingMode

As the name suggests, defines the directions of the notification.

If the Binding Mode set to TwoWay then on change of Source the Target will be Updated and like wise on change of Target the Source will be Updated, if the Binding Mode set to OneWayToTarget then in case Source was changed the Target will be Updated but in case of Target change the Source will remain with no change and finally if the Binding Mode set to OneWayToSource then in case Target was changed the Source will be Updated but in case of Source change the Target will remain with no change.

CLRPropertiesBinding

Represents the binding of two properties of two object, in case the Binding Mode is TwoWay then both Classes should implement INotifyPropertyChanged.

The CLRPropertiesBinding Class has two main Constructors:

public CLRPropertiesBinding(object source, string sourceProperty, object target, 
        string targetProperty, CLRBindingMode mode, IValueConverter converter )

This constructor receives as parameters two objects and the name of the properties to be bounded, but what is more important are the two last parameters:

  • CLRBindingMode - which was covered previously but important to notice that it has default value of TwoWay.
  • IValueConverter - which allows to bind properties of different types by implementing the IValueConverter Interface, for more information click here.

The second constructor is:

public CLRPropertiesBinding(object source, object target, string property, 
    CLRBindingMode mode,IValueConverter converter) : this(source, property, target, property, mode, converter) { }

Only difference between them is that the second one used if the name of the Source property is the same as the name of the Target property.

CLRBinding

This is just some simple Class that manages the bindings, the ideas is that for each pair of Classes there is a single instance of CLRBinding that manages and contains many instances of CLRPropertyBinding where each CLRPropertyBinding represents a pair of two properties bounded toghether.

Using the code

Well to use the code is always simpler to explain by an example. lets consider two Classes: Class1 and Class2, both implementing INotifyPropertyChanged as follows:

class Class1 : INotifyPropertyChanged
{
    private string m_Count;
    public string StringCount
    {
        get { return m_Count; }
        set
        {
            if (m_Count!=value)
            {
                m_Count=value;
                OnPropertyChanged("StringCount");
            }
        }
    }
 
    #region INotifyPropertyChanged
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        public void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
 
   #endregion
}
 
class Class2 : INotifyPropertyChanged
{
    private int m_Count;
    public int IntCount
    {
        get { return m_Count; }
        set
        {
            if (m_Count!=value)
            {
                m_Count=value;
                OnPropertyChanged("IntCount");
            }
        }
    }
 
    #region INotifyPropertyChanged
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        public void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
 
   #endregion
}

For the example to be more interesting notice that in Class1 the property StringCount is of type string and in Class2 the property IntCount is of type int, and for binding them together the following see the following example of the IValueConverter implementation.

class StringToIntConverter :IValueConverter
{
    #region IValueConverter Members
 
    public object Convert(object value, Type targetType, object parameter, 
           System.Globalization.CultureInfo culture)
    {
        int _value = (int)value;
        return _value.ToString(); 
    }
 
    public object ConvertBack(object value, Type targetType, 
           object parameter, System.Globalization.CultureInfo culture)
    {
        string _value = (string)value;
        int _IntValue;
        return (int.TryParse(_value, out _IntValue)) ? _IntValue : 0;
    }
 
    #endregion
}

Important to notice that in IValueConverter the Method Convert invoked upon target update and ConvertBack upon Source Update.

Now that all components are implemented the following code snippet actually demonstrates the the use with a simple main method.

static void Main(string[] args)
{
    //instantiate the classes to be bound
    Class1 class1 = new Class1();
    Class2 class2 = new Class2();
    
     // instantiate the converter
    StringToIntConverter converter = new StringToIntConverter();
   
    // create the binding class that managing the group of ProipertyBinding
    CLRBinding.CLRBinding binding = new CLRBinding.CLRBinding();
 
    // instantiate the property binding between object class1 as target and
    // class2 as source, between their properties StringCount and IntCount
    CLRPropertiesBinding PropertyBinding = new CLRPropertiesBinding(class2,
      "IntCount", class1, "StringCount", CLRBindingMode.TwoWay, converter);
    
    // add the PropertyBinding to the Binding class
    binding.Add(PropertyBinding);
   
    //activate the binding
    binding.Bind();
 
    // the binding is TwoWay hence to it change in one will change the other
    class1.StringCount = "100";
    class2.IntCount = 50;
}

Possible Improvements

Both methods of IValueConverter contains in addition of the value and type two more parameter, the CultureInfo, and parameter.

Currently both are not quite in use, the CultureInfo always set to CurrentCulture and the parameter is always null, if there will be a need for these parameters it would be a possible impprovement.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here