CodeProject
Introduction
In AProperties and Bindings outside of WPF Revisited, I presented the 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 discussed 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 will it 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 possibility that such a 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 the 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 implementing 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;
}
previousPropGetter.PropertyChangedEvent += (obj) =>
{
_theSetter.TheObj = obj;
};