Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C# 5.0

WPF-less Property Bindings in Depth - One Way Property Bindings (WPF Concepts Outside of WPF - Part 2)

Rate me:
Please Sign up or sign in to vote.
4.96/5 (38 votes)
17 Jul 2015CPOL21 min read 48.5K   261   56   28
Implementing powerful property bindings in plain C#

Introduction

As I wrote before, WPF (Windows Presentation Foundation) introduced a number of new programming paradigms which make it possible to create an architecture with great re-use and separation of concerns.

These new paradigms include

  • Attached Properties
  • Bindings
  • Recursive Tree Structures (Logical and Visual Trees)
  • Templates (Data and Control Templates can be re-used for creating and modifying such tree structures)
  • Routed Events (events that propagate up and down the tree structures)

 

A number of patterns widely used in WPF was built on top of these paradigms, e.g. MVVM and Behavior pattern.

MVVM is achieved by separating between View and View Model. View Model represents the non-visual skeleton of the application. View - visual meat that mimics the structure and actions of the View Model (skeleton), but is much more complex than the View Model. Communications between the View and the View Model are accomplished primarily via binding the View properties to the View Model ones so, that when something changes in the View Model, the View would change also and vice versa.

The interesting thing is that the paradigms and patterns introduced by WPF, are much bigger than WPF itself and can be used in purely non-visual applications. In fact, I strongly believe, that WPF paradigms is a qualitative leap in programming theory comparable in magnitude to the OOP breakthroughs after decades of procedural programming.

I believe that Microsoft architects, who came up with those paradigms, did not realize themselves that they can be easily adapted to be used outside of WPF world.

While the WPF concepts are generic, the WPF implementation is not - it is strongly tied to the Visuals and to the WPF Single Threaded Apartment threading model.

In this series of articles I am presenting a generic C# (non-WPF) way of implementing the paradigms listed above. Whenever I can, I am also trying to improve the WPF implementations, making them more generic and faster.

In my previous article, available at Plain C# implementation of WPF Concepts - Part 1 AProps and Introduction to Bindings I described implementation of Attached Properties (AProps) in plain C#, outside of WPF. Also I showed several usage examples of plain C# Binding functionality.

In this article, I plan to describe the Plain C# Property Binding implementation in detail.

In the next article I plan to write more about Plain C# binding, including the Collection Bindings and also presenting the Bind markup extension for using these non-WPF binding in XAML.

The plan of this article is the following

  • I briefly describe how the WPF bindings work.
  • I give usage examples of plain C# binding.
  • I discuss simple property getters and setters implementations. Simple property getters and setters are the building blocks for composite path bindings, but also can be used by themselves to create simple bindings.
  • Examples of simple bindings usage are given.
  • A detailed description of composite binding implementation is provided.

 

Some WPF background is desirable but not required for reading this article as most of the samples are completely non-visual and explained in detail.

WPF Bindings

WPF provides the following binding types:

  • Property Binding
  • Multibindings (including PriorityBinding).
  • Collection Binding (implicit only)

 

Let us take a look at those bindings in detail

WPF Property Binding

Property binding binds a property specified with respect to a source object to a property on a target object.

The purpose of the Property Binding is to make sure that the source and target properties mimic each other - they do not have to be equal, but their dependency can be determined by a Binding Converter.

Image 1

The picture above shows how the property binding works.

As you can see, on the picture, there is a source object and a target object. Binding is denoted by a dashed line with arrows at both ends. A property on the target object is bound to a property specified by a path on the source object - the source property does not have to exist right on the source object - it can be specified by a complex path. Broken 'Source Property Path' line shows a path with each line segment specifying a path link - a property on the parent object.

WPF binding has several modes:

  • OneTime - only sets target property value once.
  • OneWay - sets the target property to correspond to the source property (does not change the source property if target changes)
  • TwoWay - synchronizes source and target properties when any one of them changes.
  • OneWayToSource - changes source property value to correspond to the target, but not vice versa.

 

Note, that there is also an implicit initial behavior when a binding is set between the source and the target. Irrespectively of the binding mode, the WPF binding always sets the target property value to be in sync with the source property value, during binding initialization, and not vice versa.

WPF binding is set on the target object, so that (unless it is explicitly removed of overridden), its life-cycle matches that of the target object.

There are 3.5 ways of specifying the source object for a binding (this is BTW a question I like to ask when interviewing people for WPF positions):

  1. If no source object is specified explicitly - the binding assumes that the source object is given by the DataContext property of the target object. (I call this half a way of specifying the source object:-))
  2. Using Source property one can specify the source directly (in XAML one can set the Source property using StaticResource markup extension)
  3. ElementName property can be used only in XAML and using it one can specify another XAML element to be the source object for the binding to its Name.
  4. RelativeSource property can be specified to find an source object up the Visual or Logical Trees.

 

There is also a concept of a FallbackValue and TargetNullValue in WPF bindings. If specified, the FallbackValue will be set on the target, if the path to the source property does not exist and the TargetNullValue will be set on the target, if the source property is null.

One important limitation on all the WPF bindings is that the target property has to be a dependency property or an attached property.

WPF Multibindings

Image 2

Multibinding binds multiple source property values to a single target property value.

Usual Multibinding is one way from source to target, but can also be two way or target to source, in case all the source values can be reconstructed from a single target value.

As shown on the picture above, the Multibinding consists of multiple individual bindings to different source properties on (possibly) different source objects. All those binding results will be converted to a single value by the Multibinding converter.

Multibinding detects when one of its source properties changes and triggers the target property change.

WPF Collection Bindings

Image 3

Collection bindings are defined only implicitly in WPF. When one bind the ItemsSource property of an ItemsControl object to an ObservableCollection<T> (or any collection that implements INotifyCollectionChanged interface) one creates a Collection binding.

The resulting collection of Visual items will mimic the original non-visual collection: if an item is created and inserted, or moved, or removed within the source collection, the corresponding visual item will be created or inserted, moved or removed within the visual collection correspondingly.

The creation algorithm for the new visual items is defined by ItemTemplate and ItemContainerStyle properties. The original non-visual item always becomes the DataContext for the new Visual item.

Plain C# Bindings

Introduction

I implemented the most important features of the WPF property and collection bindings in Plain C#. The collection binding in my implementation becomes explicit and can be performed on any two collections also within C# code.

I also created Bind Markup Extension to use the bindings in XAML.

I have not implemented yet a plain C# Multibinding but this should be coming in the future.

In this article I'll be talking only about plain property binding implementation.

In several ways my binding implementation is more generic then WPF implementation:

  • My implementation does not require the target property to be an attached property or an AProp (see Plain C# implementation of WPF Concepts - Part 1 AProps and Introduction to Bindings for AProp definition). The target property can be a simple C# property.
  • The target property does not have to belong to a target object - it can be given by a path with respect to a target object.
  • The binding is not required to be tied to the target object - it can exist on its own.

 

One important feature of WPF binding, is missing in my implementation however - ability to specify the source object up a tree structure - something provided by RelativeSource property. I plan to add such capability in the future, though.

Plain Property Binding Example

Let us start with a sample from previous article, only now we shall talk more about how the bindings are implemented.

The code for this sample is located under PropertyBindingTests solution.

Here is the source of the Program.Main() method:

public static void Main()
{
    // create the Contact object (binding's source object)
    Contact joeContact = new Contact
    {
        FirstName = "Joe",
        LastName = "Doe",

        HomeAddress = new Address { City = "Boston" },
    };

    // Create the PrintModel (binding's target object)
    PrintModel printModel = new PrintModel();
    Console.WriteLine("Before binding the printModel's Home City is null");
    printModel.Print();

    //create the binding
    OneWayPropertyBinding<object, object> homeCityBinding = new OneWayPropertyBinding<object, object>();

    // set the binding's links from the source object to the source property are "HomeAddress" and "City"
    CompositePathGetter<object> homeCitySourcePathGetter =
        new CompositePathGetter<object>
        (
            new BindingPathLink<object>[]
            {
                new BindingPathLink<object>("HomeAddress"),
                new BindingPathLink<object>("City"),
            },
            null
        );

    homeCitySourcePathGetter.TheObj = joeContact;
    homeCityBinding.SourcePropertyGetter = homeCitySourcePathGetter;

    // set the binding's links from the target object to the target property are "HomeCityPrintObj" and "PropValueToPrint"
    CompositePathSetter<object> homeCityTargetPathSetter = new CompositePathSetter<object>
    (
        new BindingPathLink<object>[]
        {
            new BindingPathLink<object>("HomeCityPrintObj"),
            new BindingPathLink<object>("PropValueToPrint")
        }
    );

    homeCityTargetPathSetter.TheObj = printModel;
    homeCityBinding.TargetPropertySetter = homeCityTargetPathSetter;

    // do the actual binding between the source and the target
    // by calling Bind() method on the binding.
    homeCityBinding.Bind();


    Console.WriteLine("\nAfter binding the printModel's Home City is Boston");
    printModel.Print();

    joeContact.HomeAddress.City = "Brookline";

    Console.WriteLine("\nHome City change is detected - now Home City is Brookline");
    printModel.Print();

    joeContact.HomeAddress = new Address { City = "Allston" };

    Console.WriteLine("\nHome Address change is detected - now Home City is Allston");
    printModel.Print();


    printModel.HomeCityPrintObj = new PrintProp("Home City");
    Console.WriteLine("\nWe change the whole target link, but the binding keeps the target up to date:");
    printModel.Print();
}  

Here is what you get when you run this sample:

Before binding the printModel's Home City is null
Home City: null

After binding the printModel's Home City is Boston
Home City: Boston

Home City change is detected - now Home City is Brookline
Home City: Brookline

Home Address change is detected - now Home City is Allston
Home City: Allston

We change the whole target link, but the binding keeps the target up to date:
Home City: Allston  

The source of the sample's binding is the joeContact object of type Contact. It has HomeAddress property of type Address. Address in turn has a property City of type string - and this the source property of our binding.

The target object for the binding is printModel of type PrintModel. It has a property HomeCityPrintObj of type PrintProp. PrintProp has a property PropValueToPrint of type object and this is our binding's target property.

So, our binding, binds HomeAddress.City property on joeContact object to HomeCityPrintObj.PropValueToPrint property on the printModel object.

Note, that unlike the WPF binding, this binding can have plain (neither attached nor AProp) properties as its target - all of the properties we presented above are plain properties.

Also note, that while WPF binding requires that the target property should be defined on a target object, we allow having a complex path from the target object to the target property - in our case the path consists of two links.

Some properties that our binding uses fire INotifyPropertyChanged.PropertyChanged event. This is not a requirement, however - it is only needed if we want to be able to change the target property when the corresponding property changes. In our example all the source properties, and the target property HomeCityPrintObj corresponding to the first link within the target path, fire that event on change. That means that any change will be detected whenever any part of the chain changes.

In particular, within this sample, we show that the target property changes when we change joeContact.HomeAddress.City to another string ("Brookline") or when we change the whole joeContact.HomeAddress to another Address:

joeContact.HomeAddress = new Address { City = "Allston" };

Even when we change the target's HomeCityPrintObj:

printModel.HomeCityPrintObj = new PrintProp("Home City");

the new HomeCityPrintObj gets correct value for its PropValueToPrint property via the binding.

If one (or some) of the links within the source path, or target path were not firing PropertyChanged event, the binding would still be able to detect all the rest of link changes.

Binding Implementation

The core binding class is OneWayPropertyBinding<SourcePropertyType, TargetPropertyType>.

If the source and target property types are not known, one can use a type neutral subclass:

public class OneWayPropertyBinding : OneWayPropertyBinding<object, object>  

We are going to be using mostly a type neutral implementation, since for complex paths it might be a bit difficult to foresee all the links' types and also because we are providing a XAML markup extension for our binding and XAML is known not to be able to handle generics well (unfortunately).

Since our binding does not have to be defined on the target object, we can simulate a Two Way binding by providing two OneWayBinding objects one from source to the target and the other from the target to the source. The infinite loops are prevented by checking for equality within the property setters.

Let us take a look at the definition of the binding class:

public class OneWayPropertyBinding<SourcePropertyType, TargetPropertyType> : 
        IPropertyBinding<SourcePropertyType, TargetPropertyType>, 
        IBinding, 
        IRegistrableObject  

It implements three interfaces: IPropertyBinding<SourcePropertyType, TargetPropertyType> IBinding and IRegisrableObject.

We'll discuss the need for IRegistableObject a little later, while the other two interfaces will be explained now.

IBinding is a generic binding interface - both Collection and Property binding classes implement it:

public interface IBinding : IRegistrableObject
{
    // specifies initial synchronization between the binding source and target
    void InitialSync();

    // creates the binding
    void Bind(bool doInitialSync = true);

    // removes the binding
    void UnBind(); 
} 

It provides three methods described above by the comments.

InitialSync() method allows to specify initial synchronization at the time when the binding is creates - usually the Target property is getting its value from the source property during the initial sync, but it can be vice versa also.

The argument doInitialBinding to method Bind(...) allows to turn off the initial synchronization when do not want it to take place. This is important in case of the two way bindings composed (as was stated above) of two one-way bindings - we do not want the reverse binding to also do the initial synchronization.

Let us now take a peek at IPropertyBinding interface:

public interface IPropertyBinding<SourcePropertyType, TargetPropertyType> 
{
    // used for source property value retrieval
    IObjWithPropGetter<SourcePropertyType> SourcePropertyGetter { get; set; }

    // used for target property value setting
    IObjWithPropSetter<TargetPropertyType> TargetPropertySetter { get; set; }
} 

The interface has two properties - SourcePropertyGetter for retrieving the source value and TargetPropertySetter for setting the value on the target property are defined as IObjWithPropGetter and IObjWithPropSetter interfaces correspondings.

Let us dive into those interfaces:

public interface IObjWithPropGetter<PropertyType> : 
    IObjectSetter, 
    IPropGetter<PropertyType>
{
    // returns true is the object is set, false otherwise
    bool HasObj
    {
        get;
    }
}  

IObjWithPropGetter derives from two very simple interfaces IObjectSetter and IPropGetter. Let us look into them:

public interface IPropGetter<PropertyType>
{
    // fires when the property changes
    // its argument is new property value
    event Action<PropertyType> PropertyChangedEvent;

    // forces PropertyChangedEvent to fire
    // (it is needed e.g. when when two properties
    // are bound - the source property should 
    // trigger the target property change even
    // if the source property does not change)
    void TriggerPropertyChanged();
}  

It consists of PropertyChangedEvent which should fire when a property is changed and of TriggerPropertyChanged() method that can force the event firing whenever it is called (even if the property is not changing at that time).

And here is the code for IObjectSetter

public interface IObjectSetter
{
    // sets TheObj property
    object TheObj
    {
        set;
    }
}   

In general IObjWithPropGetter fires the PropertyChangedEvent passing to it the object's property value, when TheObj is changed or when the property changes on it. This way we cover the initial synchronization and property change together.

Now let us look at IObjWithPropSetter interface:

public interface IObjWithPropSetter<propertytype> : IObjectSetter, IPropSetter<propertytype>
{

}  
</propertytype></propertytype>

It derives from IObjectSetter (explained above) and IPropSetter interfaces:

public interface IPropSetter<propertytype>
{
    // sets the target property
    void Set(PropertyType property);
}  
</propertytype>

The Set method sets the target property but only if the target object exists and not null.

Now, let us look at some implementations of these interfaces.

Simple Property Getters and Setters Implementation

In this sub-section I describe implementation of the simple property getters and setters - those that correspond to a single link path. They can be used by themselves, but they also can be used as building blocks for composite getters and setters - those used for complex source and target paths. The composite property getters and setters will be described below.

There are two abstract classes GenericSimplePropWithDefaultGetter<PropertyType> and GenericSimplePropSetter<PropertyType> from which many implementations of the interfaces described above are derived from. These classes provide implementation functionality common to all getters and setters.

public abstract class GenericSimplePropWithDefaultGetter<PropertyType> : 
    IObjWithPropGetter<PropertyType>
{
    // default value to be passed to PropertyChangedEvent
    // when the
    PropertyType _defaultValue;

    // called to clear the 'old' object
    // when TheObj property changes
    protected abstract void OnObjUnset();

    // Called to set the events etc on the 'new'
    // object when TheObj property changes
    protected abstract void OnObjSet();

    object _obj;
    public object TheObj
    {
        protected get
        {
            return _obj;
        }

        set
        {
            if (_obj == value)
                return;

            if (HasObj) // clear the old object
            {
                // clear the event handlers from
                // the 'old' object before overriding it
                OnObjUnset();
            }

            _obj = value;

            if (HasObj)
            {
                // set the event handlers
                // on the 'new' object after it 
                // has been set
                OnObjSet();
            }

            TriggerPropertyChanged();
        }
    }

    public bool HasObj
    {
        get
        {
            return _obj != null;
        }
    }

    public GenericSimplePropWithDefaultGetter
    (
        PropertyType defaultValue = default(PropertyType)
    )
    {
        _defaultValue = defaultValue;
    }

    ~GenericSimplePropWithDefaultGetter()
    {
        //this.TheObj = null;
    }

    // Event from IPropGetter interface
    // that signifies that the property was changed on
    // an object or that the object was changed itself
    public event Action<PropertyType> PropertyChangedEvent;

    // gets Property Value from an object
    public abstract PropertyType GetPropValue();

    // Triggers the PropertyChangedEvent firing 
    // passing to it the corresponding property value if 
    // HasObj is true or _defaultValue otherwise
    public void TriggerPropertyChanged()
    {
        if (PropertyChangedEvent != null)
        {
            PropertyType propValue;

            if (HasObj)
            {
                // if object exists - get the property value from the object
                propValue = GetPropValue();
            }
            else
            {
                // if the object does not exist set the propValue to default
                propValue = _defaultValue;
            }

            // fire the PropertyChangedEvent event
            // passing propValue to it
            PropertyChangedEvent(propValue);
        }
    }
}  

HasObj property checks if the TheObj is null.

TriggerPropertyChanged() is the most important function of the class - it fires the PropertyChangedEvent passing to it the correct property value. If HasObj is false, it passes the default value (which can be set within the class constructor or is set to default(PropertyType) by default). If HasObj is true, it uses GetPropValue() abstract method to get the property value from the object.

There are two more abstract methods within the class: OnObjUnset() and OnObjSet(). Both are called within TheObj property setter. OnObjUnset() clears the event handlers from the 'old' object, before it is overridden, while OnObjSet() sets the event handlers on the 'new' object:

object _obj;
public object TheObj
{
    ...
    set
    {
        if (_obj == value)
            return;

        if (HasObj) // clear the old object
        {
            // clear the event handlers from
            // the 'old' object before overriding it
            OnObjUnset();
        }

        _obj = value;

        if (HasObj)
        {
            // set the event handlers
            // on the 'new' object after it 
            // has been set
            OnObjSet();
        }

        TriggerPropertyChanged();
    }
}  

Now let us look at GenericSimplePropSetter<PropertyType>

public abstract class GenericSimplePropSetter<PropertyType> :
    IObjWithPropSetter<PropertyType>
{
    // keeps last value for set by the binding
    // so that if the object is temporarily set to null
    // and then reset to another object, 
    // the binding's property value could be set on it.
    ValueHolder<PropertyType> _lastValueHolder = new ValueHolder<PropertyType>();

    // clear the event handlers from
    // the 'old' object before overriding it
    protected virtual void OnObjUnset() {}

    // set the event handlers
    // on the 'new' object after it 
    // has been set
    protected abstract void OnObjSet();

    // Set the 
    protected abstract void SetPropValue(PropertyType propValue);

    object _obj;
    public object TheObj
    {
        protected get
        {
            return _obj;
        }
        set
        {
            if (Object.ReferenceEquals(_obj, value))
                return;

            // clear the event handlers from
            // the 'old' object before overriding it
            OnObjUnset();

            _obj = value;

            // set the event handlers
            // on the 'new' object after it 
            // has been set
            OnObjSet();

            // if the binding value has been set before,
            // set it on the newly assigned object
            if (_lastValueHolder.HasBeenSet)
                Set(_lastValueHolder.TheValue);
        }
    }

    // constructor that can set TheObj property
    public GenericSimplePropSetter(object obj = null) 
    {
        TheObj = obj;
    }

    // implementation of IPropSetter<PropertyType>.Set
    // calls SetPropValue to set the Binding
    // value on TheObj
    public void Set(PropertyType propValue)
    {
        // nothing is set if there is no object
        if (_obj != null)
        {
            // set the property value on TheObj object
            SetPropValue(propValue);
        }

        // set TheValue on _lastValueHolder
        // to keep it in case TheObj changes
        _lastValueHolder.TheValue = propValue;
    }
}  

The main method of this class is Set(PropertyType propValue). If TheObj is not null, it invokes the abstract method SetPropertyValue(propValue) to set the property value on the object. In any case (even if TheObj is null) it sets the binding target value on _lastValueHolder object, which sets it on TheObj when it is set.

The _lastValueHolder is needed to hold the property value between the changes of TheObj object or when it is null.

Just like in the Getter class described above, abstract methods OnObjUnset() and OnObjSet() are called the TheObj property is changed to unset the 'old' and set the 'new' objects correspondingly:

public object TheObj
{
    ...
    set
    {
        if (Object.ReferenceEquals(_obj, value))
            return;

        if (_obj != null)
        {
            // clear the event handlers from
            // the 'old' object before overriding it
            OnObjUnset();
        }

        _obj = value;

        if (_obj != null)
        { 
            // set the event handlers
            // on the 'new' object after it 
            // has been set
            OnObjSet();
        }

        // if the binding value has been set before,
        // set it on the newly assigned object
        if (_lastValueHolder.HasBeenSet)
            Set(_lastValueHolder.TheValue);
    }
}  

Now let us look at various sub-classes of GenericSimplePropWithDefaultGetter<PropertyType> and GenericSimplePropSetter<PropertyType>.

Let us look at the file PlainPropGetterAndSetter.cs. It contains a number of getter and setter classes for plain (i.e. non-AProp) getters and setters.

NotifiedPropWithDefaultGetter<PropertyType> is the base abstract class for all 'plain' property getters and setters. It is called NotifiedPropWithDefaultGetter because it adapts the objects implementing INotifyPropertyChanged interface to call its TriggerPropertyChanged() method when they fire INotifyPropertyChanged.PropertyChanged event.

public abstract class NotifiedPropWithDefaultGetter<PropertyType> 
    : GenericSimplePropWithDefaultGetter<PropertyType>
{
    string _propName;
    Func<object, object> _propertyGetter = null;

    // returns INotifyPropertyChanged object if 
    // TheObj implements the interface, otherwise return null,
    INotifyPropertyChanged NotifyingObj
    {
        get
        {
            return TheObj as INotifyPropertyChanged;
        }
    }

    // if the object implements INotifyPropertChanged
    // it removes the PropertyChanged event handler obj_PropertyChanged
    // from it.
    protected override void OnObjUnset()
    {
        _propertyGetter = null;

        if (NotifyingObj != null)
        {
            NotifyingObj.PropertyChanged -= obj_PropertyChanged;
        }
    }

    // abtstract function that should return a property getter
    protected abstract Func<object, object> GetPropGetter(object obj, string propName);

    // if the object implements INotifyPropertChanged
    // it adds a PropertyChanged event handler obj_PropertyChanged
    // to it.
    protected override void OnObjSet()
    {
        _propertyGetter = GetPropGetter(TheObj, _propName);

        if (NotifyingObj != null)
            NotifyingObj.PropertyChanged += obj_PropertyChanged;
    }

    // checks if the name of the property that triggered
    // PropertyChanged event on the NotifyingObj, is the 
    // same as that of this property getter. 
    // If yes, it fires TriggerPropertyChanged method.
    void obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName != _propName)
            return;

        TriggerPropertyChanged();
    }

    // sets the property name and the default 
    // value to return
    public NotifiedPropWithDefaultGetter
    (
        string propName,
        PropertyType defaultValue = default(PropertyType)
    ) : base(defaultValue)
    {
        _propName = propName;
    }


    // returns the property valus
    public override PropertyType GetPropValue()
    {
        return (PropertyType)_propertyGetter(TheObj);
    }
}  

This class attaches obj_PropertyChanged() event handler to the INotifyPropertyChanged.PropertyChanged event of TheObj object (if TheObj implements such interface). Then when TheObj fires the event, it compares the name of the property with its _propName field and if they are the same, it calls TriggerPropertyChanged() method.

The abstract function GetPropValue is overridden to call _propertyGetter on TheObj and the _propertyGetter delegate is returned by GetPropGetter(...) abstract method:

// abtstract function that should return a property getter
protected abstract Func<object, object> GetPropGetter(object obj, string propName);  

There are two concrete realizations of this class provided within the code: PlainPropWithDefaultGetter<PropertyType> and MapPropWithDefaultGetter<PropertyType>. The former gets a plain property from an object, the latter assumes that the objects is IDictionary and the property name is the key and returns the value based on the key. MapPropWithDefaultGetter<PropertyType> implementation can be used e.g. for implementing bindings of the dynamic properties.

There are also setter classes for these getters: PlainPropertySetter<PropertyType> and MapPropertySetter<PropertyType>. They are both derived from PlayPropertySetterBase<PropertyType> class:

public abstract class PlainPropSetterBase<PropertyType> : GenericSimplePropSetter<PropertyType>
{
    Action<object, object> _propertySetter = null;

    string _propName;
    public string ThePropName
    {
        get
        {
            return _propName;
        }
    }

    // sets the property setter based on the object
    protected override void OnObjSet()
    {
        if (TheObj == null)
            return;

        _propertySetter = GetPropSetter(TheObj, _propName);
    }

    // returns the property setter delegate
    protected abstract Action<object, object> GetPropSetter(object obj, string propName);

    public PlainPropSetterBase(string propName) : base()
    {
        Init(propName);
    }

    public PlainPropSetterBase(object obj, string propName)
        : base(obj)
    {
        Init(propName);
    }

    // invokes the property setter delegate to 
    // set the property value on TheObj
    protected override void SetPropValue(PropertyType propValue)
    {
        if (TheObj == null)
            return;

        _propertySetter(TheObj, propValue);
    }

    void Init(string propName)
    {
        _propName = propName;
    }

}  

PlayPropertySetterBase<PropertyType> class defines an abstract method

protected abstract Action<object, object> GetPropSetter(object obj, string propName);  

that returns a delegate for setting the object's property.

Here is the code for PlainPropWithDefaultGetter<PropertyType>:

public class PlainPropWithDefaultGetter<PropertyType> : 
    NotifiedPropWithDefaultGetter<PropertyType>
{
    Func<object, object> _currentPropGetter = null;

    public PlainPropWithDefaultGetter
    (
        string propName,
        PropertyType defaultValue = default(PropertyType)
    )
        : base(propName, defaultValue)
    {

    }

    Type _oldObjType = null;
    // returns the property getter delegate
    protected override Func<object, object> GetPropGetter(object obj, string propName)
    {
        if (obj == null)
            return null;

        Type newObjType = obj.GetType();

        // only change the property getter if the
        // newObjType is different from the _oldObjType
        if (newObjType != _oldObjType)
        {
            // this is an expesive operation involving 
            // compiling a LINQ  expression 
            _currentPropGetter = CompiledExpressionUtils.GetUntypedCSPropertyGetter(obj, propName);
            _oldObjType = newObjType;
        }

        return _currentPropGetter;
    }
}  

The main thing that this class does - it implements an abstract function GetPropSetter(...). The implementation checks if TheObj type has changed and if it is, it calls CompiledExpressionUtils.GetUntypedCSPropertyGetter(obj, propName) function that returns the property getter delegate by creating a LINQ expression and compiling it. For those interested in how it is done, here is the link Expression based Property Getters and Setters. The reason I am using the Expressions and not Reflection is that Expression based getters and setters are much faster as you can see from the reference above.

MapPropwithDefaultGetter<PropertyType> is even simpler:

public class MapPropWithDefaultGetter<PropertyType> : NotifiedPropWithDefaultGetter<PropertyType>
{
    Func<object, object> _mapPropGetter = null;
    public MapPropWithDefaultGetter
    (
        string propName,
        PropertyType defaultValue = default(PropertyType)
    )
        : base(propName, defaultValue)
    {
        _mapPropGetter = (theObj) =>
        {
            IDictionary mapPropContainer = theObj as IDictionary;

            return mapPropContainer[propName];
        };

    }
    protected override Func<object, object> GetPropGetter(object obj, string propName)
    {
        return _mapPropGetter;
    }
}  

Its GetPropGetter(...) method returns a delegate that returns a value from an IDictionary.

Now let us take a look at the setters. Here is the code for PlainPropertySetter<PropertyType>:

public class PlainPropertySetter<PropertyType> : PlainPropSetterBase<PropertyType>
{
    Action<object, object> _currentSetter = null;
    Type _oldObjType = null;
    protected override Action<object, object> GetPropSetter(object obj, string propName)
    {
        if (obj == null)
            return null;

        Type newObjType = obj.GetType();

        if (newObjType != _oldObjType)
        {
            // for speed's sake change the setter only if the new object type is different
            // this is an expensive operation 
            // that involves compiling a LINQ Expression
            _currentSetter = CompiledExpressionUtils.GetUntypedCSPropertySetter(obj, propName);
            _oldObjType = newObjType;
        }

        return _currentSetter;
    }

    public PlainPropertySetter(string propName)
        : base(propName)
    {
    }

    public PlainPropertySetter(object obj, string propName) : base(obj, propName)
    {
    }
}  

Again we use CompiledExpressionUtils method GetUntypedCSPropertySetter to return a property setter delegate by compiling a LINQ expression.

And here is the code for MapPropertySetter<PropertyType>:

public class MapPropertySetter<PropertyType> : PlainPropSetterBase<PropertyType>
{
    Action<object, object> _mapPropertySetter = null;

    protected override Action<object, object> GetPropSetter(object obj, string propName)
    {
        return _mapPropertySetter;
    }

    void Init()
    {
        _mapPropertySetter = (theObj, propValue) =>
        {
            IDictionary map = theObj as IDictionary;

            map[ThePropName] = propValue;
        };
    }

    public MapPropertySetter(string propName)
        : base(propName)
    {
        Init();
    }

    public MapPropertySetter(object obj, string propName)
        : base(obj, propName)
    {
        Init();
    }
}  

The property setter sets the key-value pair on an IDictionary with key being the property name and value being the value we want to set on it.

Now take a look at the file APropGetterAndSetter.cs. For the explanations of what AProps are take a look at my previous article at lain C# implementation of WPF Concepts - Part 1 AProps and Introduction to Bindings

There are three classes defined in APropGetterAndSetter.cs file. One of them APropGetter<PropertyType> has been deprecated and I won't discuss it here.

Class APropWithDefaultGetter<PropertyType> is derived from GenericSimplePropWithDefaultGetter<PropertyType> just like the plain property classes discussed above:

public class APropWithDefaultGetter<PropertyType> : GenericSimplePropWithDefaultGetter<PropertyType>
{
    AProp<object, PropertyType> _aProp;

    protected override void OnObjUnset()
    {
        // unset AProp individual object property change handler
        _aProp.RemoveOnPropertyChangedHandler(TheObj, OnPropChanged);
    }

    protected override void OnObjSet()
    {
       // set AProp individual object property change handler
       _aProp.AddOnPropertyChangedHandler(TheObj, OnPropChanged);
    }

    public APropWithDefaultGetter
    (
        AProp<object, PropertyType> aProp, 
        PropertyType defaultValue = default(PropertyType) 
    )
        : base(defaultValue)
    {
        _aProp = aProp;
    }

    void OnPropChanged(object obj, PropertyType oldValue, PropertyType newValue)
    {
        TriggerPropertyChanged();
    }

    // returns the AProp value of TheObj
    public override PropertyType GetPropValue()
    {
        return _aProp.GetProperty(TheObj);
    }
}  

Implemented methods OnObjSet() and OnObjUnset() set and unset the individual AProp change handlers on the object. GetPropValue() implementation returns the AProp value on the object:

public override PropertyType GetPropValue()
{
    return _aProp.GetProperty(TheObj);
}  

APropSetter<PropertyType> is derived from GenericSimplePropSetter<PropertyType>:

public class APropSetter<PropertyType> :
    GenericSimplePropSetter<PropertyType>
{
    protected override void OnObjSet()
    {
    }

    protected override void SetPropValue(PropertyType propValue)
    {
        // set the AProp on the object
        _aProp.SetProperty(TheObj, propValue);
    }

    AProp<object, PropertyType> _aProp;

    void Init(AProp<object, PropertyType> aProp)
    {
        _aProp = aProp;
    }

    public APropSetter(AProp<object, PropertyType> aProp) : base()
    {
        Init(aProp);
    }

    public APropSetter(object obj, AProp<object, PropertyType> aProp) : base(obj)
    {
        Init(aProp);
    }
}  

Samples that Use Simple Setters and Getters

The getters and setters we looked at above are called 'simple' because they only get or set a property (or an AProp) on an object. Further down we'll describe composite getters and setters that can be used for complex paths. They use the simple getters and setters as links and they allow detecting a property change or setting a property via a complex path - e.g. a property change on a plain property of an AProp of the source object can be detected.

The project that shows bindings that used simple getters and setters is called BindingsWithSimpleGettersAndSettersTests. Here is the Program.Main() method that contains the tests:

static void Main()
{
    #region Plain Property to Plain Property Binding
    Console.WriteLine("Plain Prop to Plain Prop Binding Test");
    Address address = new Address { City = "Boston" };

    PrintProp printProp = new PrintProp("City");

    OneWayPropertyBinding<string, object> cityBinding = new OneWayPropertyBinding<string, object>();

    cityBinding.SourcePropertyGetter = new PlainPropWithDefaultGetter<string>("City") { TheObj = address };
    cityBinding.TargetPropertySetter = new PlainPropertySetter<object>("PropValueToPrint") { TheObj = printProp };

    Console.WriteLine("Before binding is set the City property should be null on printProp object");
    printProp.Print();

    // bind the source to the target
    cityBinding.Bind();
    Console.WriteLine("After binding is set the City property should be 'Boston' on printProp object");
    printProp.Print();

    address.City = "Brookline";
    Console.WriteLine("After source's property was changed to 'Brookline', the target property also changes");
    printProp.Print();
    #endregion Plain Property to Plain Property Binding

    #region AProp to Plain Property Binding
    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine("AProp to Plain Prop Binding Test");

    AProp personFirstNameAProp = new AProp("Unknown"); // default is "Unknown"

    object personData = new object();

    Person person = new Person();

    OneWayPropertyBinding firstNameBinding = new OneWayPropertyBinding();
    firstNameBinding.SourcePropertyGetter =
        new APropWithDefaultGetter<object>(personFirstNameAProp, "No Name Given") { TheObj = personData };

    firstNameBinding.TargetPropertySetter = 
        new PlainPropertySetter<object>("FirstName") { TheObj = person };

    Console.WriteLine("Before the binding, the First name should be null:");

    if (person.FirstName == null)
    {
        Console.WriteLine("null");
    }
    else
    {
        Console.WriteLine(person.FirstName);
    }

    firstNameBinding.Bind();
    Console.WriteLine("After the binding, the First name should set to the AProp's default value - 'Unknown':");
    Console.WriteLine(person.FirstName);

    personFirstNameAProp.SetProperty(personData, "John");

    Console.WriteLine("After setting the AProp on the source object to 'John' the target property should be set also:");
    personFirstNameAProp.SetProperty(personData, "John");
    Console.WriteLine(person.FirstName);

    #endregion AProp to Plain Property Binding
}  

Here is a printout you get once you run this sample:

Plain Prop to Plain Prop Binding Test
Before binding is set the City property should be null on printProp object
City: null
After binding is set the City property should be 'Boston' on printProp object
City: Boston
After source's property was changed to 'Brookline', the target property also changes
City: Brookline


AProp to Plain Prop Binding Test
Before the binding, the First name should be null:
null
After the binding, the First name should set to the AProp's default value - 'Unknown':
Unknown
After setting the AProp on the source object to 'John' the target property should be set also:
John  

We demo two bindings - one that connects a plain property City on object of type Address to another plain property PropValueToPrint on an object of type PrintProp.

The other binding connects source AProp personFirstNameAProp on a plain object personData = new object(). To a plain property FirstName on an object of type Person.

Of course, we can also have AProp as the target and plain property as the source, or we can use two AProps one as the target and the other as the source.

We can also use the dictionary based (Map) properties as sources and targets while using the corresponding getters and setters that we described above. This can come handy for dynamic objects.

Composite Property Bindings

Here we are going to describe how to define a binding with composite property getters and setters. In fact the first sample in this article was an example of such bindings. The source property was City on a property HomeAddres on the source object of type Contact. The target property was PropValueToPrint on a property HomeCityPrintObj on the target object of type PrintModel.

Here I give another sample of a composite binding - with one property plain and another AProp both within the source and the target paths. After presenting the example, I describe the implementation of the composite property getters and setters.

This composite binding sample is located under CompositeBindingTests project. Here is its Program.Main() method:

public static void Main()
{
    AProp<object, object> workAddress = new AProp<object, object>(null);

    object sourceObject = new object();

    Address sourceAddress = new Address { City = "Boston" };

    workAddress.SetProperty(sourceObject, sourceAddress);

    AProp<object, object> secondWorkCity = new AProp<object, object>("Unknown");

    Contact targetObject = new Contact
    {
        FirstName = "Joe",
        LastName = "Smith",
        WorkAddress = new Address()
    };

    OneWayPropertyBinding addressBinding = new OneWayPropertyBinding();

    addressBinding.SourcePropertyGetter = new CompositePathGetter<object>
    (
        new BindingPathLink<object>[]
        {
            new BindingPathLink<object> { TheAProp = workAddress}, // AProp link
            new BindingPathLink<object> { PropertyName = "City" }  // Plain property link 
        },
        "Unknown City" // default
    )
    { TheObj = sourceObject };

    addressBinding.TargetPropertySetter = new CompositePathSetter<object>
    (
        new BindingPathLink<object>[]
        {
             new BindingPathLink<object> { PropertyName = "WorkAddress" }, // Plain property link 
             new BindingPathLink<object> { TheAProp = secondWorkCity}  // AProp link
        }
    )
    { TheObj = targetObject };

    Console.WriteLine("Before the binding the target value is AProp's default - 'Unknown'");
    Console.WriteLine(secondWorkCity.GetProperty(targetObject.WorkAddress));

    addressBinding.Bind();

    Console.WriteLine("After the binding the target value is 'Boston'");
    Console.WriteLine(secondWorkCity.GetProperty(targetObject.WorkAddress));


    sourceAddress.City = "Brookline";
    Console.WriteLine("After the source value changed to 'Brookline' the target changes also");
    Console.WriteLine(secondWorkCity.GetProperty(targetObject.WorkAddress));
}

Here is the output of this sample

Before the binding the target value is AProp's default - 'Unknown'
Unknown
After the binding the target value is 'Boston'
Boston
After the source value changed to 'Brookline' the target changes also
Brookline  

As you can see - we bind City property of the workAddress AProp on the sourceObject to secondWorkCity AProp on WorkAddress plain property of the targetObject (of type Contact).

In other words, we bind a plain propety defined on an AProp on the source object to an AProp defined on a plain property on the target object and our composite binding can still handle it.

BindingPathLink objects are used to specify the kind of the property - for plain property we simply specify a property name as a string, while for AProp we set TheAProp property of the BindingPathLink to the AProp object, e.g.:

addressBinding.SourcePropertyGetter = new CompositePathGetter<object>
(
    new BindingPathLink<object>[]
    {
        new BindingPathLink<object> { TheAProp = workAddress}, // AProp link
        new BindingPathLink<object> { PropertyName = "City" }  // Plain property link 
    },
    "Unknown City" // default
)  

Note, one should NOT set both TheAProp and PropertyName properties on the BindingPathLink at the same time.

Composite Getter and Setter Implementation

Composite Getter and Setter classes are defined within CompositePathPropertyGetterAndSetter.cs file under NP.Paradigms project.

Here is the code for CompositePathGetter<PropertyType>:

public class CompositePathGetter<PropertyType> : IObjWithPropGetter<PropertyType>
{
    object _defaultValue;

    public event Action<PropertyType> PropertyChangedEvent;

    // Call TriggerPropertyChanged() method
    // on the last property getter. This will
    // trigger PropertyChangedEvent firing on the 
    // CompositePathGetter object itself, passing
    // a correct property value to it.
    public void TriggerPropertyChanged()
    {
        if (PropertyChangedEvent == null)
            return;

        LastPropGetter.TriggerPropertyChanged();
    }

    // Last property getter in the chain.
    IObjWithPropGetter<object> LastPropGetter
    {
        get
        {
            return _propGetters[_propGetters.Count - 1];
        }
    }

    IEnumerable<BindingPathLink<object>> _pathLinks;

    List<IObjWithPropGetter<object>> _propGetters = new List<IObjWithPropGetter<object>>();
    public List<IObjWithPropGetter<object>> PropGetters
    {
        get
        {
            return _propGetters;
        }
    }

    public CompositePathGetter(IEnumerable<BindingPathLink<object>> pathLinks, object defaultValue)
    {

        _pathLinks = pathLinks;

        _defaultValue = defaultValue;

        // create the first property getter in the chain to be 
        // of type SimplePropGetter that returns TheObj itself as the property value.
        IObjWithPropGetter<object> previousPropGetter = new SimplePropGetter<object>();
        _propGetters.Add(previousPropGetter);

        // we are creating a chain of Property Getters according to the path links. 
        // when the property on the previous property getter changes, 
        // the new property value becomes TheObj (object) of the next property getter
        foreach (var pathLink in _pathLinks)
        {
            IObjWithPropGetter<object> propGetter = pathLink.GetPropertyGetter();

            _propGetters.Add(propGetter);

            if (previousPropGetter != null)
            {
                previousPropGetter.PropertyChangedEvent += (obj) =>
                    {
                        propGetter.TheObj = obj;
                    };
            }

            previousPropGetter = propGetter;
        }

        // connect PropertyChangeEvent of the last getter
        // to fire the PropertyChangedEvent of the 
        // whole CompositePathGetter object. 
        // If LastPropGetter does not have an object, 
        // we pass the _defaultValue as the argument
        // to the event, otherwise we pass the real value. 
        previousPropGetter.PropertyChangedEvent += (propVal) =>
            {
                if (this.PropertyChangedEvent == null)
                    return;

                if (!LastPropGetter.HasObj)
                {
                    PropertyChangedEvent( (PropertyType) _defaultValue);
                }
                else
                {
                    PropertyChangedEvent((PropertyType)propVal);
                }
            };
    }


    object _obj;
    public object TheObj
    {
        set
        {
            if (_obj == value)
                return;

            _obj = value;

            // set the first of the prop getters in the chain.
            this._propGetters[0].TheObj = _obj;
        }
    }

    public bool HasObj
    {
        get
        {
            return _obj != null;
        }
    }
}  

To create the CompositePathGetter we are passing a collection of BindingPathLink<object> objects to its constructor. For each of the links, we create corresponding simple property getter by using the method GetPropertyGetter():

IObjWithPropGetter<object> propGetter = pathLink.GetPropertyGetter();  

This function returns the correct property getter based on the ThePropertyKind property of the link:

public virtual IObjWithPropGetter<PropertyType> GetPropertyGetter()
{
    switch (ThePropertyKind)
    {
        case PropertyKind.Plain:
            return new PlainPropWithDefaultGetter<PropertyType>(PropertyName, DefaultValue);
        case PropertyKind.Map:
            return new MapPropWithDefaultGetter<PropertyType>(PropertyName, DefaultValue);
        case PropertyKind.AProp:
            return new APropWithDefaultGetter<PropertyType>(TheAProp, DefaultValue);
        default:
            return null;
    }
}  

ThePropertyKind on the path link can be set based on how the path link is constructed. For example, setting its PropertyName will result in ThePropertyKind being set to PropertyKind.Plain while, setting its TheAProp property will set ThePropertyKind to PropertyKind.AProp.

We arrange property getter objects obtained from the path links in a chain so that firing the PropertyChangeEvent on the previous link within the chain, triggers setting TheObj property of the next getter (which in turn triggers firing PropertyChangedEvent on that getter etc.). The last property getter in the chain triggers firing of PropertyChangedEvent on the CompositePathGetter<PropertyType> object itself - making sure that the binding propagates the change to its property setter object. This chain structure ensures that whichever link in chain changes, CompositePathGetter object will notice it and propagate the events.

Here is the code for CompositePathSetter<PropertyType>.

public class CompositePathSetter<PropertyType> : IObjWithPropSetter<PropertyType>
{
    IObjWithPropSetter<object> _theSetter = null;

    // the last link in the chain and the only setter
    // within it
    public IObjWithPropSetter<object> TheSetter
    {
        get
        {
            return _theSetter;
        }
    }

    // The chain of property getters - all links
    // except for the last one.
    List<IObjWithPropGetter<object>> _propGetters;
    public List<IObjWithPropGetter<object>> PropGetters
    {
        get
        {
            return _propGetters;
        }
    }

    public CompositePathSetter(IEnumerable<BindingPathLink<object>> pathLinks)
    {
        // designate the setter path link as last link
        BindingPathLink<object> theSetterPathLink = pathLinks.Last();

        _theSetter = theSetterPathLink.GetPropertySetter();

        // Just like in case of CompositePathGetter
        // create a chain of getters up to the last link
        // in order to detect and propogate TheObj property changes.
        _propGetters =
            pathLinks
                .TakeWhile((pathLink) => (!Object.ReferenceEquals(pathLink, theSetterPathLink)))
                .Select((pathLink) => pathLink.GetPropertyGetter())
                .ToList();

        // set all the prop getters exactly like in
        // CompositePathGetter constructor.
        // So that we set TheObj property of the 
        // next getter to be the value of the previous one
        IObjWithPropGetter<object> previousPropGetter = null;
        foreach (var propGetter in _propGetters)
        {
            if (previousPropGetter != null)
            {
                previousPropGetter.PropertyChangedEvent += (obj) =>
                {
                    propGetter.TheObj = obj;
                };
            }

            previousPropGetter = propGetter;
        }

        // the last getter's PropertyChangedEvent
        // is setting TheObj property on the setters. 
        if (previousPropGetter != null)
        {
            // set the last property getter to the set the setter
            previousPropGetter.PropertyChangedEvent += (obj) =>
            {
                _theSetter.TheObj = obj;
            };
        }
    }

    // Setting the property value 
    // sets it on the setter (the last link in the chain)
    public void Set(PropertyType propertyValue)
    {
        _theSetter.Set(propertyValue);
    }

    // set TheObj property of the first link in the chain
    public object TheObj
    {
        set 
        {
            if (_propGetters.Count > 0)
            {
                _propGetters[0].TheObj = value;
            }
            else
            {
                _theSetter.TheObj = value;
            }
        }
    }
}  

Here we are also creating a chain of objects in a fashion very similar to the CompositePathGetter, only the last object in this chain is a setter. This will serve the same purpose - whenever an object is changed within the path, the CompositePathGetter will notice the change and propagate the required binding value all the way to the target object.

Summary

In this article I describe my implementation of Binding paradigm in plain C# outside of WPF. In a sense my implementation is more powerful - it does not require the targets to be Dependency or Attached properties or even AProps, also it allows the target property to be specified as by a complex path with respect to the target object.

Another important property of this binding is that, unlike WPF binding, it does not have to be tied to the binding's target. This means e.g. that we can use the same binding class for forward and reverse bindings.

One important feature, however, is not implemented yet - ability to bind to a property to a source up a tree (similar to the RelativeSource AncestorType capability of WPF. I plan to implement it in the future.

In the next article, I am going to talk more about the bindings. In particular, I will discuss

  • Implementing two way bindings and binding modes
  • Collection binding
  • Bind markup extension that allows to use this binding implementation in XAML

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
AnswerRe: Another good job Pin
Nick Polyak6-Jul-15 3:59
mvaNick Polyak6-Jul-15 3:59 
GeneralMy vote of 5 Pin
Chrris Dale5-Jul-15 14:18
Chrris Dale5-Jul-15 14:18 
GeneralRe: My vote of 5 Pin
Nick Polyak5-Jul-15 15:40
mvaNick Polyak5-Jul-15 15:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.