Code Location
The code for this blog post can be downloaded from EventBindingTests.zip.
The solution file EventBindingTests.sln is located under EventBindingTests\TESTS\EventBindingTests folder.
Introduction
Here, I continue a series of blog posts about implementing WPF concepts outside of WPF.
In fact here, I am going to describe a concept/pattern that does not exist in WPF but, based on my experience will be useful in WPF-esq universe – the Event Binding.
WPF creates various hierarchies of objects – there is a logical tree, there is a visual tree. When programming MVVM, there is also unspoken, but widely used hierarchy of the view models – e.g. a top level View Model might contain some member representing another View Model or it might contain a collection of the view models representation, e.g., rows in a table or entries in a ListView
.
Many times, there should be an action from a sub-View Model to the one of its ‘ancestors’ in the View Model hierarchy. This might happen, e.g. when a visual action is invoked on the sub-View Model but should result in a change on its ‘ancestor’ level. The simplest example would be a remove button on each of the items within a list view. If implemented view the View Model patterns, the remove button will have access only to the sub-View Model corresponding to individual item. However, the remove action should happen on the collection that contains the items’ View Models, i.e. a level higher. The way to implement it would be to create a ‘remove action’ event that fires at the item View Model level. When item becomes part of the collection in the higher level View Model – it adds a handler to the event that actually removes the item. The higher level View Model needs to manage adding and removing handlers to the items as they are added or removed to or from the collection or as the collection of items is getting totally overridden by a different collection.
The purpose of the Event Binding is precisely to make it easier to manage the event handlers added at the ‘ancestor’ level to the ‘descendant’ item events.
Demonstration of What One Can Do With Event Binding
The main program is in Programs.cs file of EventBindingTests
project. It demonstrates binding an event handler to a single object or to a collection of objects.
Single Object Event Binding
Here is the single object event binding code (do not try to read too much into it since it is explained step by step below:
PopulatedOrganization wonkaFactory = new PopulatedOrganization();
Organization chocolateDepartment = new Organization();
#region SINGLE OBJECT EVENT BINDING
EventBinding<Organization, string> eventBinding =
new EventBinding<Organization, string>();
eventBinding.SourceObj = wonkaFactory;
eventBinding.SourcePathLinks =
StringCodePathResolver.ResolveStringPath("TheMostImportantDepartment").ToList();
eventBinding.Bind();
eventBinding.EventName = "SomethingHappenedInOrgEvent";
eventBinding.TheEvent += eventBinding_SomethingHappenedInTheDepartment;
chocolateDepartment.FireSomethingHappenedEvent
("Augustus Gloop went to the chocolate creek. (Before the department added - should not show)" );
wonkaFactory.TheMostImportantDepartment = chocolateDepartment;
chocolateDepartment.FireSomethingHappenedEvent("Augustus Gloop is out of the game (should show)");
eventBinding.Unbind();
#endregion SINGLE OBJECT EVENT BINDING
Here is the description of what is going on in the code. Organization
class has a property TheMostImportantDepartment
which is also of Organization
type. Organization
also has an event SomethingHappenedInOrgEvent
. This event is of the type SomethingHappenedDelegate
which is similar to Action<string>
. Method FireSomethingHappenedEvent(string message)
fires the event passing the message
to it.
We want to bind the SomethingHappenedInOrgEvent
on the TheMostImportantDepartment
property of the organization to a handler at the main program level. For this purpose, we use the Event Binding:
EventBinding<Organization, string> eventBinding =
new EventBinding<Organization, string>();
eventBinding.SourceObj = wonkaFactory;
eventBinding.SourcePathLinks =
StringCodePathResolver.ResolveStringPath("TheMostImportantDepartment").ToList();
eventBinding.Bind();
eventBinding.EventName = "SomethingHappenedInOrgEvent";
The above code does the binding. Note that the binding is already there even though the
TheMostImportantDepartment
property has not been set yet.
Now we add the event handler to the Event Binding and not to the original event:
eventBinding.TheEvent += eventBinding_SomethingHappenedInTheDepartment;
This event handler will simply print the message argument from the event.
Now, if we try to fire the event on the chocolateDepartment
object – nothing should change, because TheMostImportantDepartment
property of the wonkaFactory
object is still not set to the chocolateFactory
:
chocolateDepartment.FireSomethingHappenedEvent("Augustus Gloop went to the chocolate creek.
(Before the department added - should not show)" )
Now we set the property and fire an event again and the corresponding message should be printed on the console:
chocolateDepartment.FireSomethingHappenedEvent("Augustus Gloop is out of the game (should show)");
Note that the Event Binding takes full care of figuring out if the property is null
or not and making the event binding behave accordingly as long as the binding notification is on (for simple properties – that means firing INotifyPropertyChanged.PropertyChanged
event when the TheMostImportantDepartment
property changes. Similar notifications are available for AProperties
or Attached/Dependency
properties – but the SourcePathLinks
will have to reflect the corresponding PropertyKind
.
Note also that even though we considered a path containing only one path link – we can use arbitrary path links of arbitrary length for Event Bindings as long as each link provides binding notifications.
Collection Event Binding
Collection Event Binding provides even more dramatic refactoring. Not only it takes case of collection being reset, but also if the collection implements INotifyCollectionChanged
interface (i.e. ObservableCollection
, it adds or removes proper handlers when the items of the collection are added or removed correspondingly.
An organization has AllDepartments
property of type ObservableCollection<Organization>
. We want to set the collection, add a couple of departments to it use Event Binding to bind the SomethingHappenedInOrgEvent
on the collection objects to our event handler. Here is the corresponding code:
#region COLLECTION EVENT BINDING
wonkaFactory.AllDepartments = new ObservableCollection();
wonkaFactory.AllDepartments.Add(chocolateDepartment);
CollectionEventBinding<Organization, string> collectionEventBinding =
new CollectionEventBinding<Organization, string>();
collectionEventBinding.SourceObj = wonkaFactory;
collectionEventBinding.SourcePathLinks =
StringCodePathResolver.ResolveStringPath("AllDepartments").ToList();
collectionEventBinding.Bind();
collectionEventBinding.EventName = "SomethingHappenedInOrgEvent";
collectionEventBinding.TheEvent += collectionEventBinding_TheEvent;
Organization gumDepartment = new Organization();
gumDepartment.FireSomethingHappenedEvent("We had great sales
(Before the department is added - should not show)");
wonkaFactory.AllDepartments.Add(gumDepartment);
gumDepartment.FireSomethingHappenedEvent("We had great sales
(After the department is added - should show)");
wonkaFactory.AllDepartments.Remove(gumDepartment);
gumDepartment.FireSomethingHappenedEvent("We had great sales
(After the department is Removed - should not show)");
#endregion COLLECTION EVENT BINDING
The binding code is sufficiently similar to the case of a single object so that we do go over each step again in detail. I’d like to re-iterate, however, that the CollectionEventBinding
will manage the event handlers on each of the members of the collection both in case the whole collection is re-assigned (if all the path links to the collection have binding notifications) or in case elements are added or removed to or from it (if the collection implements INotifyCollectionChanged
interface).
Implementation Notes
The central class for both EventBinding
and CollectionEventBinding
implementation is NP.Paradigms.EventBindingBase<ObjectType, EventObjectType>
. It provided the actual binding from the actual object in the hierarchy to the Event Binding‘s property TheObj
. This class has two generic parameters: ObjectType
and EventObjectType
. ObjectType
is the type of the object that we bind to – in case of a single event binding – it is the same as the type of the object that contains the event (EventObjectType
), while in case of a collection event binding, it is a collection of objects of type EventObjectType
.
This class contains two important abstract
methods Disconnect()
and Reconnect()
that control removing or setting the event handler on the corresponding object(s). These methods are overridden in concrete implementation of EventBinding
and CollectionEventBinding
functionality.
This class defines also the name of the method that will be attached to the bound object(s) events: "EventHadler"
. This method is also defined in the sub-classes.
The reflection based actual implementation of adding and removing the handlers to the object is located within NP.Paradigms.EventManager
class.
Class SingleObjectEventBindingBase
is derived from EventBindingBase
it overrides Disconnect()
and Reconnect()
methods to act on a single object.
A number of EventBinding
classes with various generic parameters specifying different possible arguments to the event is derived from SingleObjectEventBindingBase
class.
CollectionEventBindingBase
class is also derived from EventBindingBase
by overriding the same functions Disconnect()
and Reconnect()
and specifying some handling when items are added or removed to or from the collection.
A number of CollectionEventBinding
classes with various generic parameters is also derived from CollectionEventBindingBase
class.
Conclusion
In this blog post, I describe a new concept of Event Binding which is not part of WPF but should come in handy for programming using WPF related concepts (whether it used in WPF or outside of WPF).
<img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/nickssoftwareblog.wordpress.com/574/" /> <img alt="" border="0" height="1" src="http://stats.wordpress.com/b.gif?host=nickssoftwareblog.com&blog=4912334&post=574&subd=nickssoftwareblog&ref=&feed=1" width="1" />