Introduction
WPF (Windows Presentation Foundation) introduced many new programming paradigms which, to the best of my knowledge, were not utilized by any other framework or language. These paradigms make WPF programming very powerful, concise and perfect for separations of concerns. Once you understand how to use these paradigms you can produce a lot of functionality with a few lines of code while maintaining separation of concerns, thus making your software easy to extend, modify and debug.
The interesting thing about WPF paradigms is that many of them do not have anything to do with WPF or Visual Programming or even C# language and can be implemented and applied for non-visual programming and in other languages, e.g. Java and JavaScript, producing the same advantages for building completely different applications.
The way Microsoft team implemented the WPF paradigms couples them strongly to WPF. As a result, in order to use them outside of WPF, even in C#, another implementation is required. In fact, my feeling is that WPF's architects and developers themselves did not quite understand the terrific conceptual breakthroughs that they made and therefore did not push for more wide-spread acceptance of those paradigms.
In these series of articles I am presenting my own implementation of most of the WPF paradigms in plain (non-WPF) C#. Many of these paradigms are implemented a bit differently - usually in a more generic way then their WPF counterparts. I also dropped some features that I do not use often in WPF. Originally these paradigms were briefly described in a series of my blog article - you can see them at the code project.
In the future I plan to create similar packages for Java and JavaScript (or, perhaps, TypeScript).
Here is a list of WPF-less paradigm implementations that will be described in these article series:
- Attached properties
- Bindings
- DataTemplates
- Generic Trees (functional trees) - instead of Visual and Logical trees.
- RoutedEvents
- Behaviors
In this installment of the series I discuss re-implementing Attached Properties and Bindings outside of WPF. I also discuss greater-then-WPF, but promoted-by-WPF concepts of Non-Invasive Object Modification (NIOM) and Data and Functionality Mimicking (DAFM).
At this point I did not enable multithreading or choosing threads for the functionality - so all of the samples are single threaded and a change handling happens in the same thread as the change invocation. I plan to address threading sometime in the future.
Some knowledge of WPF is desirable but not required for reading this article, as I provide non-WPF samples illustrating every one of the features described here.
Code Structure
The test solution files can be found within folders under 'Tests' directory.
Also 'Tests' directory contains a NP.Tests.GenericTestObjects
library used for creating (very simple) test objects if they are used in multiple tests.
Folder 'NP.Paradigms' contains the generic WPF-less code implementing the WPF paradigms described in this article.
Non-WPF Implementation of Usage of Attached Properties (AProps) and the Concept of Non-Invasiveness
Attached Properties in WPF
Important Note for WPF developers reading this article: when talking about Attached Properties below, I also include Dependency Properties - I consider them to be a version of Attached Properties that are required to be defined in a class that uses them.
Attached properties in WPF were introduced in part in order for objects with potentially hundreds of properties (but most of those properties having default values) to take less space than in a straight forward implementation - so called sparse implementation.
At the same time, they address a number of other, very important issues:
- They allow attaching any property to any object without that property being defined on that object in advance.
- A property change handler can be registered so that when an attached property changes on an object, it is being invoked. This allows some really interesting processing to be performed after an Attached Property change and makes the foundation of attaching and detaching behaviors to an object.
- Attached Properties can be used as WPF Binding target. In our binding implementation we are going to relax this condition - so that not only our Attached Properties (or AProps as I call them), but also usual properties can be used as binding targets.
- WPF Attached Properties can be made to propagate down the visual tree. I have not implemented this part outside of WPF yet.
Attached Properties, Behaviors and the Concept of Non-Invasive Object Modification (NIOM)
There is a software development concept of non-invasive object modification (NIOM) that has been widely used, but not formalized yet; this is my attempt at formalization.
Plain C# allows to 'Add' methods to a class without class modification - the so called extension methods can be defined in a static class outside of the class on whose objects they are called. The non-invasiveness in this case is purely nominal of course - they are not 'real' methods on the modified class; e.g. they do not have access to the private or protected class members, but it is still important.
WPF's attached properties are defined in a static class and can be attached to any object derived from DependencyObject
class. This allows associating some data with an object without modifying the object's class. This is very important for separation of concerns. E.g. assume that you have a generic object Widget
. Widget
which represents a window with some information in it. Some Widget
s have headers and some don't. One way of implementing a Widget
with a header would be to create a class WidgetWithHeader
that inherits from class Widget
. This, however, will prevent using any other super-class for the WidgetWithHeader
(because of the lack of Multiple Implementation Inheritance in C#). Instead, we can create a header for the Widget
as a completely separate control and attach it to the Widget
s that require them using an Attached Property. In that case we can even use the same Attached Property to attach the header to non-Widget objects that might still need headers.
Of course, the same approach of attaching properties can be also applied to non-Visual objects.
Behaviors are the most powerful way of modifying the objects non-invasively. In WPF the behaviors are usually attached to objects using Attached Properties. The behaviors themselves provide event handlers for the object's events. Using behaviors, you can create very complex functionality for an object outside of the object's class.
Again there is nothing in Behaviors that prevents them from being used outside of WPF and I gave examples of non-visual Behaviors in View-View Model based WPF and XAML Implementational Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 2)
AProps Usage Samples
I call my outside-of-WPF implementation of Attached Properties AProps in order to differentiate it from the WPF implementation.
I'll start by showing how to use AProps, and later talk about how they were implemented - as it is my firm conviction that first, the users need to understand what a concept is needed for, and only later and less importantly - how it was implemented.
The samples presented in this sub-section are located under 'Tests/APropTests' and 'Tests/APropsIndividualHandlersTests' folders. Both are referencing a very simple Person
class defined within NP.Tests.GenericTestObjects
project. Here is the code for class Person
:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return FirstName + " " + LastName;
}
}
In NP.Tests.APropsTests
project, we show how to
- Create an AProp for specific object and property types, specifying default property values and generic pre and post change handlers (by generic I mean that they are going to fire on every object if its corresponding AProp changes).
- Set the property on any object of the corresponding type and observe the handlers firing.
Here is the code for the Program.Main()
method of the tester project:
public static void Main()
{
AProp<Person, bool> isStudentAProp = new AProp<Person, bool>
(
false, (obj, oldVal, newVal) => {
Console.WriteLine
(
"\tIsStudent AProp is about to change on person " + obj + " from " + oldVal + " to " + newVal
);
return true; },
(obj, oldVal, newVal) => {
Console.WriteLine("\tIsStudent AProp changed on the person " + obj);
}
);
Person nick = new Person { FirstName = "Nick", LastName = "Polyak" };
bool isNickStudent = isStudentAProp.GetProperty(nick);
Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);
isStudentAProp.SetProperty(nick, false);
isStudentAProp.SetProperty(nick, true);
isNickStudent = isStudentAProp.GetProperty(nick);
Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);
}
Line AProp<Person, bool> isStudentAProp = new AProp<Person, bool>(...)
creates the AProp.
Just like in case of a WPF Attached Property, AProp is a separate object. Unlike a WPF Attached Property - AProp object does not have to be defined as static within a static class as long as it is visible everywhere you want to use it for getting or setting a value on an object. In our case we are using it only within Program.Main()
method and so, we can define it within the same function.
Also, note that AProp is more type safe than WPF's Attached Property - its two type arguments define the type of an object to which we want to attach and the type of the attached property correspondingly - in our case we want to be able to attach property IsStudent
of type bool
to objects of type Person
, so we have generic types defined as <Person, bool>
.
AProps can be attached to any Reference Type objects - not Value Types are allowed. This is again, much more generic than WPF - which allows to use only classes derived from DependencyObject
class for that purpose.
Now, let us look at the whole constructor
AProp<Person, bool> isStudentAProp = new AProp<Person, bool>
(
false, (obj, oldVal, newVal) => {
Console.WriteLine
(
"\tIsStudent AProp is about to change on person " + obj + " from " + oldVal + " to " + newVal
);
return true; }, (obj, oldVal, newVal) => {
Console.WriteLine("\tIsStudent AProp changed on the person " + obj);
}
);
The first argument to the constructor is the default value (property value returned if no value has been ever set on the object in question).
The second argument to the constructor is a delegate of type BeforePropertyChangedDelegate<ObjectType, PropertyType>
:
public delegate bool BeforePropertyChangedDelegate<ObjectType, PropertyType>
(
ObjectType obj,
PropertyType oldPropertyValue,
PropertyType newPropertyValue
);
It allows operating the object whose AProp is being set, the old property value and the new property value. It also gives you last chance to cancel the property change by returning boolean value false
.
We implement it as a Lambda:
(obj, oldVal, newVal) =>{
Console.WriteLine
(
"\tIsStudent AProp is about to change on person " + obj + " from " + oldVal + " to " + newVal
);
return true; }
As you see - our handler is printing a message that the property is about to change.
Next argument is the post change handler of type OnPropertyChangedDelegate<ObjectType, PropertyType>
:
public delegate void OnPropertyChangedDelegate<ObjectType, PropertyType>
(
ObjectType obj,
PropertyType oldPropertyValue,
PropertyType newPropertyValue
);
In our example it is also a Lambda:
(obj, oldVal, newVal) =>{
Console.WriteLine("\tIsStudent AProp changed on the person " + obj);
}
that prints a message stating that the property has changed.
We shift the messages printed within pre and post change handlers by a tab "\t"
in order not to confuse them with the message printed within the Main
method itself.
Note that both pre and post change handlers can be passed as null
s (or not passed at all), in that case no generic handler will be fired on the property change.
The rest of the code is pretty much explained by the in-line comments
Person nick = new Person { FirstName = "Nick", LastName = "Polyak" };
bool isNickStudent = isStudentAProp.GetProperty(nick);
Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);
isStudentAProp.SetProperty(nick, false);
isStudentAProp.SetProperty(nick, true);
isNickStudent = isStudentAProp.GetProperty(nick);
Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);
We create a Person
object nick
and use the AProp's setters and getter to set and get the properties on that object. Here is an example of getting isStudentAProp
value from object nick
:
bool isNickStudent = isStudentAProp.GetProperty(nick);
and here is an example of setting isStudentAProp
value on object nick
to true
:
isStudentAProp.SetProperty(nick, true);
Note, that if the property is being set to the same value as before (default or not), nothing will change and no handlers will be fired as we demonstrate in the code.
Default IsStudent AProp on object nick is False
IsStudent AProp is about to change on person Nick Polyak from False to True
IsStudent AProp changed on the person Nick Polyak
Default IsStudent AProp on object nick is True
Using AProp Change Handlers on Individual Objects
The second AProp sample is located under NP.Tests.APropsIndividualHandlersTests
solution. It shows how to add a Post change AProp handler to an individual object - so that it will only be fired when AProp changes on that object but not on any other object.
This is important functionality that Attached Properties do not have - the only way I could imitate it - is by creating a binding with the Attached Property whose change we want to detect being the source of the binding and using the Target's Attached Propertie's change handler for change detection.
The test's code located within Program.Main()
method is simpler than that of the previous example:
public static void Main()
{
AProp<Person, bool> isStudentAProp = new AProp<Person, bool>
(
false );
Person nick = new Person { FirstName = "Nick", LastName = "Polyak" };
Person joe = new Person { FirstName = "Joe", LastName = "Doe" };
isStudentAProp.AddOnPropertyChangedHandler(nick, IndividualPropChangeHandler);
isStudentAProp.SetProperty(nick, true);
isStudentAProp.SetProperty(joe, true);
}
private static void IndividualPropChangeHandler(Person obj, bool oldPropertyValue, bool newPropertyValue)
{
Console.WriteLine("This is INDIVIDUAL AProp value change handler fired on object " + obj.ToString());
}
We create isStudentProp
as an AProp without any generic change handlers.
Two Person
objects nick
and joe
are created - in order to show that when we attach an individual change handler to nick
, changing the AProp on joe
won't trigger it.
Here is how we attach the isStudentAProp
individual change handler to object nick
:
isStudentAProp.AddOnPropertyChangedHandler(nick, IndividualPropChangeHandler);
When running this test you see that the handler is only fired for object nick
:
This is INDIVIDUAL AProp value change handler fired on object Nick Polyak
Clearing AProps
In order to reset an AProp on an object to the default, one can use APropClearAProperty(Object obj)
method. E.g. in the example abover, one can clear the isStudentAProp
on object nick
by calling isStudentAProp.ClearAProperty(nick)
. All the individual change handlers will also be removed from nick
object.
AProps vs Attached Properties
The examples above present complete capabilities of the AProps. They are considerably less complex than those of the Attached Properties, but they cover everything I ever needed from the Attached Properties except for the ability to inherit property values down the visual tree. Unlike Attached Properties, AProps can be defined on any refernce type object, not only on those descendent from DependencyObject
. On top of this, AProps are more type safe and allow registering change handlers for individual objects.
AProp Implementation
There are two ways to associate a (property) value with an object. One way is to add this value to the object's class - in this case each object of this class will contain a reference to this value.
The other, less usual way of creating such association is to create a collection of Values accessible by the object. This can be done by using Dictionary<ObjectType, ValueType>
with objects being the keys. Then (providing you have a reference to the dictionary), you can get the property value for each object. This is how Microsoft implemented Attached Properties and this is how I implemented AProps.
AProp implementation code is located in file AProp.cs within NP.Paradigms project.
The object-to-value dictionary is defined by class member:
ConditionalWeakTable<ObjectType, APropertyValueWrapper<ObjectType, PropertyType>> _objectToPropValueMap;
I used ConditionalWeakTable
and not the usual Dictionary
class, because it creates a weak reference to its keys and values, so that the object destruction will not be hindered by it being it being one of the keys. Moreover, the destroyed object will be automatically removed from the ConditionalWeakTable
collection. Also, ConditionalWeakTable
us multi-thread safe, so one does not need to worry about accessing and setting AProps on multiple threads.
Keys of this 'Dictionary' are the plain objects of ObjectType
, while the values are plain property values, but objects of class APropertyValueWrapper<ObjectType, PropertyType>
.
APropertyValueWrapper<ObjectType, PropertyType>
is a private class, defined in the same file. It contains the AProp value as
internal PropertyType APropertyValue { get; set; }
It also contains a weak reference to the object that has that value:
internal WeakReference<ObjectType> ObjReference { get; private set; }
and an event serving for attaching the individual AProp change event handlers:
internal event OnPropertyChangedDelegate<ObjectType, PropertyType> OnPropertyChangedEvent = null;
The event can be fired via the following code:
internal void FireOnPropertyChangedEvent(PropertyType oldPropertyValue)
{
if (OnPropertyChangedEvent != null)
{
ObjectType obj;
if (ObjReference.TryGetTarget(out obj))
{
OnPropertyChangedEvent(obj, oldPropertyValue, APropertyValue);
}
}
}
The above also shows why we need a reference to the object 'containing' the AProp value - we need it in order to pass it as the first argument to the event handler.
Class AProp<ObjectType, PropertyType>
provides the methods we showed above for setting and getting the AProp values on objects. Also it provides PropertyType _defaultValue
class member that defines a value to be returned by AProp.GetProperty(ObjectType obj)
method in case the AProp is not set on the passed object (there is no corresponding entry within ConditionalWeakTable
table).
There is also a non-strongly typed version of AProp class:
public class AProp : AProp<object, object>
It is more generic, but less type safe.
Registering AProps
Class AProp<ObjectType, PropertyType>
implements a very simple interface
public interface IAPropValueGetter
{
object GetObjectAPropValue(object obj);
}
This interface allows to get the untyped AProp value of the passed object.
There is also a static class
public static class AllAProps
{
static public List<IAPropValueGetter> TheAProps
{
get
{
...
}
}
...
}
It contains a collection TheAProps
of all AProps ever defined in your application. Each AProp
object is added to AllAProps.TheAProps
collection within its constructor. There is also a built-in mechanism for removing the references to the stale (garbage collected) AProp
s.
This collection is not being used at this point, but this is the only way all of AProps within your application and potentially can be used by the tools e.g. Snoop improved to handle AProps. (Currently there is no such capability in Snoop, of course).
Non-WPF Implementation of the Bindings and the Concept of Data and Functionality Mimicking (DAFM)
Introduction
This article is already getting large, so I am going to put only a Binding teaser here with the main material following in the second installment of this series.
WPF is not the first framework to start using binding, but WPF (to the best of my knowledge) is the first framework that is binding centric. The famouse MVVM pattern is just one manifestation of what the binding can achieve.
Binding is the core of the concept that I call Data and Functionality Mimicking (DAFM).
MVVM is an example of DAFM - you create a pure non visual skeleton called View Model or VM. This skeleton can be responsible for creating some data and data collections, communication with the other parts of the application and the back end and so on.
View (or the visual part of the application) is the meat that grows around the skeleton and mimics its data and functionality. Changes to the View (e.g. user input into an editable area) can also result in changes to the View Model (Bindings can work in both directions).
Important Note: The picture above might make people think that the View Model has some knowledge of the View. THIS IS NOT TRUE! The only way the View Model can control the View is via the bindings. View, however, is aware of the View Model and can modify it through the binding and by some other means.
Two Types of Data and Two Types of Data Binding
At the high level there are two types of data structures:
- Data that can be represented by names mapped into values. A C# object of class
Person
considered in the previous sub-section is an example of such data - its FirstName
property is mapped say to value 'joe' and its LastName
property is mapped to value 'doe';
- Data that can be represented by a collection of objects of similar shape. Any collection, say of integers, will be an example of such data.
By combining the two types of data, we can create a data hierarchy - e.g. a Property of a class can contain a collection of some objects of complex types that contain multiple properties etc.
JSON is very well suited for representing the data structure hierarchy - both the name-values and the collections.
The better known WPF Binding is the one that binds two properties on two objects, i.e. it binds the name-value types of data hierarchy.
The other type of binding - is a binding between two collections - it makes the target collection mimic the structure of the source collection - when items are inserted into or deleted from the source collection, the corresponding items are also inserted into or deleted from the target collection. This type of binding is used in WPF only implicitly - when an ItemsControl
's ItemsSource
is bound to an ObservableCollection<T>
Below I present classes that implement both types of data binding.
Data Binding Samples
As promised above, I'll show some capabilities of non-WPF bindings here while a larger discussion and implementation explanations will be followed in the next article.
Property Binding Sample
This test is located under PropertyBindingTests.sln solution.
In order to demonstrate a binding with composite paths I created a hierarchy of classes.
The source object is of class Contact
. It contains HomeAddress
and WorkAddress
properties of type Address
. Address
contains City
and Street
string properties.
public class Address : INotifyPropertyChanged
{
...
public string City
{
...
}
public string Street
{
...
}
}
and
public class Contact : Person, INotifyPropertyChanged
{
...
public Address HomeAddress
{
...
}
public Address WorkAddress
{
...
}
}
Both Contact
and Address
classes implement INotifiablePropertyChanged
interface and all the properties fire PropertyChanged
event when the property changes.
The target object is of class PrintModel
. It contains HomeCityPrintObj
property of class PrintProp
. It also contains a method Print()
that calls HomeCityPrintObj.Print()
. PrintProp
class contains _propertyName
string field, PropValueToPrint
string property and method Print()
that prints property name and property value:
public class PrintProp
{
public PrintProp(string propName)
{
_propName = propName;
}
readonly string _propName;
public object PropValueToPrint { get; set; }
public void Print()
{
string strToPrint = "null";
if (PropValueToPrint != null)
strToPrint = PropValueToPrint.ToString();
Console.WriteLine(_propName + ": " + strToPrint);
}
}
and
public class PrintModel : INotifyPropertyChanged
{
...
PrintProp _homeCityPrintObj = null;
public PrintProp HomeCityPrintObj
{
private get
{
return _homeCityPrintObj;
}
set
{
if (_homeCityPrintObj == value)
return;
_homeCityPrintObj = value;
OnPropertyChanged("HomeCityPrintObj");
}
}
public PrintModel()
{
HomeCityPrintObj = new PrintProp("Home City");
}
public void Print()
{
HomeCityPrintObj.Print();
}
}
In Program.Main()
method we create a Contact
and a PrintModel
and bind the HomeAddress/City
property of the Contact
to HomeCityPrintObj/PropValueToPrint
property of the target:
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 the result of running 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
Notice that our property binding can bind via a compound path on the target (in our case it is HomeCityPrintObj/PropValueToPrint
), while WPF property binding can only bind the immediate properties on the target. In this sense our binding is more generic than that of WPF.
Notice also, that as long as the corresponding property on the source path fires INotifyPropertyChanged.PropertyChanged
event on change, the target property will be changed also: first we change the city name and then we change the whole HomeAddress
and in both cases, the change is picked up by the PrintModel
.
Another interesting result, is that even if we change a link on the target path e.g.:
printModel.HomeCityPrintObj = new PrintProp("Home City");
and this link change also triggers a PropertyChanged
event, the active binding will force the new printModel.HomeCityPrintObj
to have correct PropValueToPrint
.
In the subsequent article we'll show that our Binding
can have AProp and WPF Attached Property links both in the source and target paths.
Collection Binding Sample
Collection binding sample is located under CollectionBindingTests.sln solution.
We are using an ObservableCollection<Person>
as the Binding source and ObservableCollection<PersonVM>
as the Binding target collections.
Person
is a very simple class that we already used above. It contains FirstName
and LastName
string properties. PersonVM
inherits from Person
and adds IsEnabled
and IsVisible
properties:
public class PersonVM : Person
{
public bool IsVisible { get; set; }
public bool IsEnabled { get; set; }
public PersonVM()
{
IsEnabled = true;
IsVisible = true;
}
public PersonVM(Person p) : this()
{
this.FirstName = p.FirstName;
this.LastName = p.LastName;
}
}
In a sense, collection of Person
objects can be considered a Model and collection of PersonVM
objects can be considered a View Model.
Here is the code of the Program.Main()
method:
public static void Main()
{
ObservableCollection<Person> personCollection = new ObservableCollection<Person>();
personCollection.Add(new Person { FirstName = "Nick", LastName = "Polyak" });
personCollection.Add(new Person { FirstName = "Joe", LastName = "Doe" });
Collection<PersonVM> personVMCollection = new Collection<PersonVM>();
Console.WriteLine("Before binding personVMCollection is Empty:");
personVMCollection.PrintCollection();
OneWayCollectionBinding<Person, PersonVM> collectionBinding =
new OneWayCollectionBinding<Person, PersonVM>
{
SourceCollection = personCollection,
TargetCollection = personVMCollection,
SourceToTargetItemDelegate = (person) => new PersonVM(person) };
collectionBinding.Bind();
Console.WriteLine("After binding personVMCollection is populated with the same items as the input collection:");
personVMCollection.PrintCollection();
Console.WriteLine("When 'John Smith' person is added to the source collection, he is also added to the target collection:");
personCollection.Add(new Person { FirstName = "John", LastName = "Smith" });
personVMCollection.PrintCollection();
Console.WriteLine("When 'Nick Polyak' person is removed from the source collection, he is also removed from the target collection:");
personCollection.RemoveAt(0);
personVMCollection.PrintCollection();
}
After Bind()
method is called on the Binding, the target collection mimics the source collection, whatever changes are made to the source collection:
Before binding personVMCollection is Empty:
EMPTY
After binding personVMCollection is populated with the same items as the input collection:
Nick Polyak, Joe Doe
When 'John Smith' person is added to the source collection, he is also added to the target collection:
Nick Polyak, Joe Doe, John Smith
When 'Nick Polyak' person is removed from the source collection, he is also removed from the target collection:
Joe Doe, John Smith
In order for the mimicking to take place, the source collection should implement INotifyCollectionChanged
interface, while the target collection should implement ICollection<T>
interface, i.e. allow adding and removing items.
Summary
In this article I showed how to implement and use Attached Properties and Bindings outside of WPF, providing also some usage examples. The AProps and Bindings discussed here, are in many ways more generic and less restricting than those of WPF and can be used on any objects, not only on DependencyObjects
.
In the next article I plan to concentrate more on the bindings, provide binding implementation details, introduce the notion of an event binding and give more usage examples.