Archive for June, 2013

Composite Path Bindings outside of WPF

June 27, 2013

Composite Path Bindings outside of WPF

Introduction

In AProperties and Bindings outside of WPF Revisited I presented implementation of binding concepts using IPropGetter and IPropSetter interfaces. This implementation allowed binding a source property on a source object to a target property on a target object. Both plain properties and AProperties could be used as source and target.

The binding dicussed at the link above, has a limitation, though, in that it only deals with immediate properties of the source or the target. You cannot bind to a property with a complex path to it. This blog entry aims to resolve this problem. Not only it will provide the functionality for binding using a complex path to the source property (something the WPF binding functionality also allows), but it will show how to use a complex path at the target side as well (something that WPF does not permit).

When dealing with complex path to the source property, there is always a posibility that the such path simply does not exist. In that case the binding can provide a default value to the target property. This default value is similar to WPF binding’s FallbackValue property.

The source code for the article is located under CompositePathTests.zip file.

Sample that Uses Composite Path Bindings

The main project is CompositePathToTargetTest. Most of the sample code is located within Program.cs file.

Both source and target object of this sample are of ParentDataClass type. ParentDataClass has a property TheData of type DataClass. DataClass in turn has a property MyStringProp of type string. The sample shows how to bind MyStringProp property of the TheData property of the source object to the same path within the target object.

The class that was called BindingPath in the previous articles is renamed to BindingPathLink. This class chooses correct property “getter” and “setter” for the binding. The composite paths consist of a collection of the BindingPathLink objects. Here is how such collection is created for the source object:

CompositePathGetter sourcePathGetter =
    new CompositePathGetter
    (
        new BindingPathLink<object>[]
        {
            new BindingPathLink<object>("TheData"),
            new BindingPathLink<object>("MyStringProp"),
        },
        "A Default String"
    );

CompositePathGetter requires a collection of path links and a default value that will be sent to the target if the path to the source does not exist (remember it is similar to the FallbackValue of the WPF binding).

The setter for the binding’s target is created in a similar way:

CompositePathSetter targetPathSetter = new CompositePathSetter
    (
        new BindingPathLink<object>[]
        {
            new BindingPathLink<object>("TheData"),
            new BindingPathLink<object>("MyStringProp")
        }
    );

only here we do not need to pass the default value parameter.

Then we set the source and target objects of the source getter and target setter:

 sourcePathGetter.TheObj = sourceDataObj;
 targetPathSetter.TheObj = targetDataObject;

We set the corresponding getter and setter properties of the binding:

binding.SourcePropertyGetter = sourcePathGetter;
binding.TargetPropertySetter = targetPathSetter;

Then after we call function Bind() on the binding object, the binding becomes operational and the source value is set on the target: calling Console.WriteLine(targetDataObject.TheData.MyStringProp); will print “Hello World”.

Then if we change the source property: sourceDataObj.TheData.MyStringProp = "Hi World"; the target property will also change to “Hi World”. If we change the TheData property of the source object, the target will also reflect the change:

 sourceDataObj.TheData = new DataClass { MyStringProp = "bye bye" };

will set the target property to “bye bye”.

If TheData property of sourceDataObj is set to null, the default binding value “A Default String” will be set to the target property.

If TheData property of the targetDataObj is set to null, the old binding value is retained and will be set to the MyStringProp property of the new TheData object, if at some point it becomes non-null.

Notes on Implementation

Instead of IPropGetter<PropertyType> and IPropSetter<PropertyType> interfaces, used in the previous articles for implmenting the binding’s getter and setter, we use IObjWithPropGetter<PropertyType> and IObjWithPropSetter<PropertyType> interfaces that also allow setting the object for which the properties are read or set. This is done in order not to recreate the getter and setters every time the path’s objects are created or destroyed.

CompositeClassGetter represents a chain of IObjWithPropGetter<PropertyType> objects built from a list of BindingPathLink objects that represent the path to the source property from the source object. The PropertyChangedEvent of each IObjWithPropGetter is handled by setting the corresponding object for the next property getter. The handler for the last object is set to call the PropertyChangeEvent on the CompositeClassGetter object:

IObjWithPropGetter<object> previousPropGetter = null;
foreach (var pathLink in _pathLinks)
{
    IObjWithPropGetter<object> propGetter = pathLink.GetPropertyGetter();

    _propGetters.Add(propGetter);

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

    previousPropGetter = propGetter;
}

previousPropGetter.PropertyChangedEvent += (obj) =>
    {
        if (this.PropertyChangedEvent == null)
            return;

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

CompositePathSetter consists of IObjWithPropSetter object corresponding to the last link of the target property path and a chain of IObjWithPropertyGetter objects corresponding to the rest of the links. The PropertyChangedEvent of each of the property getters sets the object on the next property getter (or setter):

IObjWithPropGetter<object> previousPropGetter = null;
foreach (var propGetter in _propGetters)
{
    if (previousPropGetter != null)
    {
        previousPropGetter.PropertyChangedEvent += (obj) =>
        {
            propGetter.TheObj = obj;
        };
    }

    previousPropGetter = propGetter;
}

// set the last property getter to the set the setter
previousPropGetter.PropertyChangedEvent += (obj) =>
{
    _theSetter.TheObj = obj;
};