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.
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):
- 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:-))
- Using
Source
property one can specify the source directly (in XAML one can set the Source
property using StaticResource
markup extension)
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
.
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
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
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()
{
Contact joeContact = new Contact
{
FirstName = "Joe",
LastName = "Doe",
HomeAddress = new Address { City = "Boston" },
};
PrintModel printModel = new PrintModel();
Console.WriteLine("Before binding the printModel's Home City is null");
printModel.Print();
OneWayPropertyBinding<object, object> homeCityBinding = new OneWayPropertyBinding<object, object>();
CompositePathGetter<object> homeCitySourcePathGetter =
new CompositePathGetter<object>
(
new BindingPathLink<object>[]
{
new BindingPathLink<object>("HomeAddress"),
new BindingPathLink<object>("City"),
},
null
);
homeCitySourcePathGetter.TheObj = joeContact;
homeCityBinding.SourcePropertyGetter = homeCitySourcePathGetter;
CompositePathSetter<object> homeCityTargetPathSetter = new CompositePathSetter<object>
(
new BindingPathLink<object>[]
{
new BindingPathLink<object>("HomeCityPrintObj"),
new BindingPathLink<object>("PropValueToPrint")
}
);
homeCityTargetPathSetter.TheObj = printModel;
homeCityBinding.TargetPropertySetter = homeCityTargetPathSetter;
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
{
void InitialSync();
void Bind(bool doInitialSync = true);
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>
{
IObjWithPropGetter<SourcePropertyType> SourcePropertyGetter { get; set; }
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>
{
bool HasObj
{
get;
}
}
IObjWithPropGetter
derives from two very simple interfaces IObjectSetter
and IPropGetter
. Let us look into them:
public interface IPropGetter<PropertyType>
{
event Action<PropertyType> PropertyChangedEvent;
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
{
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>
{
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>
{
PropertyType _defaultValue;
protected abstract void OnObjUnset();
protected abstract void OnObjSet();
object _obj;
public object TheObj
{
protected get
{
return _obj;
}
set
{
if (_obj == value)
return;
if (HasObj) {
OnObjUnset();
}
_obj = value;
if (HasObj)
{
OnObjSet();
}
TriggerPropertyChanged();
}
}
public bool HasObj
{
get
{
return _obj != null;
}
}
public GenericSimplePropWithDefaultGetter
(
PropertyType defaultValue = default(PropertyType)
)
{
_defaultValue = defaultValue;
}
~GenericSimplePropWithDefaultGetter()
{
}
public event Action<PropertyType> PropertyChangedEvent;
public abstract PropertyType GetPropValue();
public void TriggerPropertyChanged()
{
if (PropertyChangedEvent != null)
{
PropertyType propValue;
if (HasObj)
{
propValue = GetPropValue();
}
else
{
propValue = _defaultValue;
}
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) {
OnObjUnset();
}
_obj = value;
if (HasObj)
{
OnObjSet();
}
TriggerPropertyChanged();
}
}
Now let us look at GenericSimplePropSetter<PropertyType>
public abstract class GenericSimplePropSetter<PropertyType> :
IObjWithPropSetter<PropertyType>
{
ValueHolder<PropertyType> _lastValueHolder = new ValueHolder<PropertyType>();
protected virtual void OnObjUnset() {}
protected abstract void OnObjSet();
protected abstract void SetPropValue(PropertyType propValue);
object _obj;
public object TheObj
{
protected get
{
return _obj;
}
set
{
if (Object.ReferenceEquals(_obj, value))
return;
OnObjUnset();
_obj = value;
OnObjSet();
if (_lastValueHolder.HasBeenSet)
Set(_lastValueHolder.TheValue);
}
}
public GenericSimplePropSetter(object obj = null)
{
TheObj = obj;
}
public void Set(PropertyType propValue)
{
if (_obj != null)
{
SetPropValue(propValue);
}
_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)
{
OnObjUnset();
}
_obj = value;
if (_obj != null)
{
OnObjSet();
}
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;
INotifyPropertyChanged NotifyingObj
{
get
{
return TheObj as INotifyPropertyChanged;
}
}
protected override void OnObjUnset()
{
_propertyGetter = null;
if (NotifyingObj != null)
{
NotifyingObj.PropertyChanged -= obj_PropertyChanged;
}
}
protected abstract Func<object, object> GetPropGetter(object obj, string propName);
protected override void OnObjSet()
{
_propertyGetter = GetPropGetter(TheObj, _propName);
if (NotifyingObj != null)
NotifyingObj.PropertyChanged += obj_PropertyChanged;
}
void obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != _propName)
return;
TriggerPropertyChanged();
}
public NotifiedPropWithDefaultGetter
(
string propName,
PropertyType defaultValue = default(PropertyType)
) : base(defaultValue)
{
_propName = propName;
}
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:
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;
}
}
protected override void OnObjSet()
{
if (TheObj == null)
return;
_propertySetter = GetPropSetter(TheObj, _propName);
}
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);
}
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;
protected override Func<object, object> GetPropGetter(object obj, string propName)
{
if (obj == null)
return null;
Type newObjType = obj.GetType();
if (newObjType != _oldObjType)
{
_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)
{
_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()
{
_aProp.RemoveOnPropertyChangedHandler(TheObj, OnPropChanged);
}
protected override void OnObjSet()
{
_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();
}
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)
{
_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();
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");
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}, new BindingPathLink<object> { PropertyName = "City" } },
"Unknown City" )
{ TheObj = sourceObject };
addressBinding.TargetPropertySetter = new CompositePathSetter<object>
(
new BindingPathLink<object>[]
{
new BindingPathLink<object> { PropertyName = "WorkAddress" }, new BindingPathLink<object> { TheAProp = secondWorkCity} }
)
{ 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}, new BindingPathLink<object> { PropertyName = "City" } },
"Unknown City" )
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;
public void TriggerPropertyChanged()
{
if (PropertyChangedEvent == null)
return;
LastPropGetter.TriggerPropertyChanged();
}
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;
IObjWithPropGetter<object> previousPropGetter = new SimplePropGetter<object>();
_propGetters.Add(previousPropGetter);
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 += (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;
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;
public IObjWithPropSetter<object> TheSetter
{
get
{
return _theSetter;
}
}
List<IObjWithPropGetter<object>> _propGetters;
public List<IObjWithPropGetter<object>> PropGetters
{
get
{
return _propGetters;
}
}
public CompositePathSetter(IEnumerable<BindingPathLink<object>> pathLinks)
{
BindingPathLink<object> theSetterPathLink = pathLinks.Last();
_theSetter = theSetterPathLink.GetPropertySetter();
_propGetters =
pathLinks
.TakeWhile((pathLink) => (!Object.ReferenceEquals(pathLink, theSetterPathLink)))
.Select((pathLink) => pathLink.GetPropertyGetter())
.ToList();
IObjWithPropGetter<object> previousPropGetter = null;
foreach (var propGetter in _propGetters)
{
if (previousPropGetter != null)
{
previousPropGetter.PropertyChangedEvent += (obj) =>
{
propGetter.TheObj = obj;
};
}
previousPropGetter = propGetter;
}
if (previousPropGetter != null)
{
previousPropGetter.PropertyChangedEvent += (obj) =>
{
_theSetter.TheObj = obj;
};
}
}
public void Set(PropertyType propertyValue)
{
_theSetter.Set(propertyValue);
}
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