Collection Behaviors






4.97/5 (7 votes)
I describe reusable implementation of the collection behaviors that make items behave in a certain way as long as the items belong to a collection.
Introduction
Behaviors as a pattern were first introduced for WPF by MS Expression Blend team (at least according to my knowledge). In spite of this, the behaviors do not have anything to do with the visuals. In fact, I gave several examples of the non-visual behaviors in some of my previous articles. For example, View-View Model based WPF and XAML Implementational Patterns talk about the purely non-visual "single selection" and "two last items" selection behaviors.
In general, behavior is an object that can modify the behavior of a class non-invasively, i.e., it does not require any coding changes to the class.
WPF is very rich with events and behaviors can be very useful there, but I came across a lot of cases where the behaviors are also useful for non-WPF object, including View Models.
The way the behavior works is - when it is attached to an object, it makes some modifications to the object including creating the handlers for some of the object's events. These handlers are what creates the object's behavior change. When the behavior is detached from the object, its event handlers should be removed from the object's event.
Based on the above statement, the simplest behavior will provide a single event handler on attaching to an object and remove it on detaching.
Behaviors can be stateless or with a state:
- Stateless behaviors are not aware of a specific object they are attached to - in fact, you can have only one static behavior object that is used to modify behavior of every object that needs it.
- Behaviors with the state keep a reference to the object they are modifying. Correspondingly each modified object has to have its own behavior.
Collection behaviors allow to attach a behavior to every item within the collection. The trick is to keep the behavior attached to every item that belongs to a collection and to remove it from all the items that are removed from a collection or whenever the collection is replaced.
Here is the layout of the article:
- I start this article with a refresher on what single item behavior is, providing a very simple example.
- Then I show how to attach such behavior for a collection of items so that it is attached to every item if and only if the item is part of the collection and detached if item is removed from a collection.
- Then I show how to achieve the same with much less code, by employing the
DoForEachItemCollectionBehavior
class. - Finally, I show how to further improve the code using behaviors disposable token. In particular, it shortens the collection behavior syntax, allows chaining the behavior and also allows to easily attach the behaviors outside of the collection containing class.
Code Location
You can download the code from the article or from the following Github link: Code for Collection Behavior Article.
All the testing solutions are located under TESTS folder.
Example of a Single Item Behavior
Solution TESTS/NP.Tests.SingleItemBehaviorTest/NP.Tests.SingleItemBehaviorTest.sln contains an example of PrintNotifiablePropertyBehavior
that will work on a single item.
Program.Main()
method contains the usage code:
static void Main(string[] args)
{
// create the test class
MyNotifiablePropsTestClass myTestClass = new MyNotifiablePropsTestClass();
// create the behavior
PrintNotifiablePropertyBehavior printNotifiablePropertyBehavior =
new PrintNotifiablePropertyBehavior();
// attach the behavior to the class
printNotifiablePropertyBehavior.Attach(myTestClass);
// should print (since Behavior is attached)
myTestClass.TheString = "Hello World";
// detach the behavior from the class
printNotifiablePropertyBehavior.Detach(myTestClass);
// should not print (since Behavior is detached);
myTestClass.TheString = "Bye World";
}
We create an object of class MyNotifiablePropsTestClass
containing notifiable property TheString
. We attach the PrintNotifiablePropertyBehavior
whose purpose is to print the notifiable property name and value to the console when the property changes. We change the TheString
property to "Hello World
" string
and, since the the behavior is attached, it prints...
TheString: Hello World
...to the console.
Then, we detach the behavior from the object:
// detach the behavior from the class
printNotifiablePropertyBehavior.Detach(myTestClass);
and change TheString
property to "Bye World
":
// should not print (since Behavior is detached);
myTestClass.TheString = "Bye World";
This change is not be printed since the property was detached.
The implementation of MyNotifiablePropsTestClass
is also very simple - it implements INotifiablePropertyChanged
interface and has only one notifiable property TheString
:
#region TheString Property
private string _str;
public string TheString
{
get
{
return this._str;
}
set
{
if (this._str == value)
{
return;
}
this._str = value;
this.OnPropertyChanged(nameof(TheString));
}
}
#endregion TheString Property
PringNotifiablePropertyBehavior
simply attaches a handler to INotifiablePropertyChanged.PropertyChanged
event that (using reflection) extract the property value and prints the property name and value to the console:
public class PrintNotifiablePropertyBehavior :
IStatelessBehavior<INotifyPropertyChanged>
{
// the handler
private static void NotifyiableObject_PropertyChanged
(
object sender,
PropertyChangedEventArgs e
)
{
// pring property name and value to the console
// using reflection
sender.PrintPropValue(e.PropertyName);
}
public void Attach(INotifyPropertyChanged notifyiableObject)
{
// add the handler
notifyiableObject.PropertyChanged +=
NotifyiableObject_PropertyChanged;
}
public void Detach(INotifyPropertyChanged notifyiableObject)
{
// remove the handler
notifyiableObject.PropertyChanged -=
NotifyiableObject_PropertyChanged;
}
}
Collection Behavior
Now assume that we want to achieve a similar behavior for every item in some ObservableCollection
. The items that belong to the collection should have this behavior attached to them, the items that are removed from a collection (or if the whole collection is overridden as property in some containing class) the behavior should be detached from the items. All of this should happen automatically without any extra coding outside of the class that contains the collection.
Such example is given in TESTS/NP.Tests.ItemsCollectionTest/NP.Tests.ItemsCollectionTest.sln project.
MyNotifiableCollectionTestClass
contains property TheCollection
which is an ObservableCollection
of items of MyNotifiablePropsTestClass
(described in the previous section).
Here is the testing code (from Program.Main()
method of the project):
// create the observable collection containing class
MyNotifiableCollectionTestClass collectionTestClass =
new MyNotifiableCollectionTestClass();
// create item 1.
MyNotifiablePropsTestClass item1 = new MyNotifiablePropsTestClass();
// set TheCollection property of the collection
// containing class to be an ObservableCollection
// that contains item1
collectionTestClass.TheCollection =
new ObservableCollection<MyNotifiablePropsTestClass>
(
new MyNotifiablePropsTestClass[] { item1 }
);
// change item1.TheString
// since item1 is part of the collection,
// it should print to console:
item1.TheString = "Item1: Hello World";
// create item2
MyNotifiablePropsTestClass item2 = new MyNotifiablePropsTestClass();
// add item2 to the collection
collectionTestClass.TheCollection.Add(item2);
// change item2.TheString
// since item2 is part of the collection,
// it should print to console
item2.TheString = "Item2: Hello World";
// remove item2 from collection
collectionTestClass.TheCollection.RemoveAt(1);
// since item2 is no longer
// part of the collection
// should NOT print to console
item2.TheString = "Item2: Bye Wordl";
// disconnect the whole collection (i.e. Item1)
collectionTestClass.TheCollection = null;
// since the collection property is null,
// nothing should be printed to console
// when Item1.TheString is changed.
item1.TheString = "Item1: Bye World";
When running the code, the following should be printed to the console:
TheString: Item1: Hello World
TheString: Item2: Hello World
"Bye World
" messages should not be printed since when they are changed, the items are not part of the MyNotifiableCollectionTestClass.TheCollection
property.
In order to achieve that, we provide some extra plumbing within MyNotifiableCollectionTestClass
.
We add to it methods SetItems
and UnsetItems
which accept a collection of items and add or remove the handler to their PropertyChanged
event:
// removes the PropertyChanged handler from all
// old items
void UnsetItems(IEnumerable items)
{
if (items == null)
return;
foreach (MyNotifiablePropsTestClass item in items)
{
item.PropertyChanged -= Item_PropertyChanged;
}
}
// attached the PropertyChanged handler to all
// new items
void SetItems(IEnumerable items)
{
if (items == null)
return;
foreach(MyNotifiablePropsTestClass item in items)
{
item.PropertyChanged += Item_PropertyChanged;
}
}
Then the handler for the CollectionChanged
event of the ObservableCollection
will call UnsetItems
on the old items and SetItems
on the new items:
private void _collection_CollectionChanged
(
object sender,
NotifyCollectionChangedEventArgs e
)
{
// remove handlers from the old items
UnsetItems(e.OldItems);
// add handlers to all new items
SetItems(e.NewItems);
}
Finally, within the setter of TheCollection
property, we set the CollectionChanged
handler and we also call UnsetItems
on the old collection and SetItems
on the new collection (passed by the value):
private ObservableCollection<MyNotifiablePropsTestClass> _collection;
public ObservableCollection<MyNotifiablePropsTestClass> TheCollection
{
get
{
return this._collection;
}
set
{
if (this._collection == value)
{
return;
}
if (_collection != null)
{
// remove the handler
// from the old collection
_collection.CollectionChanged -=
_collection_CollectionChanged;
}
// remove handlers from items
// in the old collection
UnsetItems(this._collection);
this._collection = value;
// add handlers to the items
// in the new collection
SetItems(this._collection);
if (_collection != null)
{
// watch for the new collection change
// to set the added and unset
// the removed items
_collection.CollectionChanged +=
_collection_CollectionChanged;
}
}
}
You can see that in order to make the class with the collection to behave the way we want, we had to add a significant amount of code.
In the next section, I'll show how this amount of extra code can be drastically shrunk by employing a reusable DoForEachItemCollectionBehavior<T>
class.
Achieving Collection Behavior with DoForEachItemCollectionBehavior<T> Class
Solution TESTS/NP.Tests.ItemsCollectionBehaviorTest/NP.Tests.ItemsCollectionBehaviorTest.sln shows the great simplification of the code. The expected behavior and the Program.Main(...)
method are exactly the same as in the prevoius section; the change is with the plumbing inside MyNotifiableCollectionTestClass
class. You can see that the class has shrunk by almost 50% - from 103 to 54 lines of code.
Here, the only extra code is to define the behavior and to attach and detach the behavior from the collection within the setter:
// define the behavior.
DoForEachItemCollectionBehavior<INotifyPropertyChanged>
_doForEachItemCollectionBehavior =
new DoForEachItemCollectionBehavior<INotifyPropertyChanged>
(
item => item.PropertyChanged += Item_PropertyChanged,
item => item.PropertyChanged -= Item_PropertyChanged
);
And here is the code for TheCollection
property getter and setter:
private ObservableCollection<MyNotifiablePropsTestClass> _collection;
public ObservableCollection<MyNotifiablePropsTestClass> TheCollection
{
get
{
return this._collection;
}
set
{
if (this._collection == value)
{
return;
}
// detach from old collection
_doForEachItemCollectionBehavior.Detach(_collection);
this._collection = value;
// attach to new collection
_doForEachItemCollectionBehavior.Attach(_collection);
}
}
Further Improvement of the Code by Using Disposable Tokens
There is a way to further sharing the code, by employing Disposable Behaviors Tokens and extension methods of NP.Paradigms.Behaviors.DoForEachBehaviorUtils
static
class.
Take a look at TESTS/NP.Tests.ItemsCollectionDisposableBehaviorTest/NP.Tests.ItemsCollectionDisposableBehaviorTest.sln solution.
Again the Program.Main(...)
method is exactly the same as in the previous two examples.
MyNotifiableCollectionTestClass
, however, is even smaller than in the prevous sample - only 52 lines.
All we need to do is to add the following class field:
// Contains the disposable token.
// Calling its Dispose()
method will
// Detach all the behaviors from the collection IDisposable _disposableBehaviors;
and to add the following code to the end of the setter:
_disposableBehaviors?.Dispose(); // detaches old behaviors
_disposableBehaviors = _collection.AddBehavior
(
item => item.PropertyChanged += Item_PropertyChanged,
item => item.PropertyChanged -= Item_PropertyChanged
);
Here, we create the behavior or even a behavior collection when the collection changes by calling DoForEachBehaviorUtils.AddBehavior
extension method. The method returns an IDisposable
that will contain all the created behaviors and will detach them when IDisposable.Dispose()
method is called.
Those who know Rx.NET, will recognize that this way of unsubscribing was borrowed from there.
By now, you might be wondering why I am saying that multiple behaviors might be detached on the token disposal even though only one behavior was created. The thing is that this method (DoForEachBehaviorUtils.AddBehavior
) also allows us to chain multiple behaviors as will be shown in the next section.
Chaining Multiple Collection Behaviors Outside of the Class that Defines the Collection
Code for this example is located under TESTS/NP.Tests.ItemsCollectionDisposableChainedBehaviorTest/NP.Tests.ItemsCollectionDisposableChainedBehaviorTest.sln solution.
The example shows how to chain multiple behaviors using AddBehavior
extension method and also how attach the behaviors to a collection outside of the class that defines it using the disposable tokens paradigm.
There are some changes to the test object. First of all, the MyNotifiableCollectionTestClass
now defines an event TheCollectionValueChangedEvent
event which fires when the value of TheCollection
property changes (within the properties setter):
public event Action<ObservableCollection<MyNotifiablePropsTestClass>>
TheCollectionValueChangedEvent = null;
#region TheCollection Property
private ObservableCollection<MyNotifiablePropsTestClass> _collection;
public ObservableCollection<MyNotifiablePropsTestClass> TheCollection
{
get
{
return this._collection;
}
set
{
if (this._collection == value)
{
return;
}
this._collection = value;
// fire collection changed event:
TheCollectionValueChangedEvent?.Invoke(_collection);
}
}
#endregion
The disposable token and setting up the behavior is moved over to Program.Main(...)
.
Static
Program
class now has a static
property NumberItemsInCollection
. The purpose of the second behavior within the chain is to maintain this property to match the number of items within collectionTestClass.TheCollection
collection. The setter of this property also sends the string
containing the property value to console:
static int _numberItemsInCollection = 0;
public static int NumberItemsInCollection
{
get
{
return _numberItemsInCollection;
}
set
{
if (_numberItemsInCollection == value)
return;
_numberItemsInCollection = value;
// write the changed property to console
Console.WriteLine($"Number items in collection: {_numberItemsInCollection}");
}
}
At the very top of Program.Main(...)
method's body, after the collectionTestClass
is created, we add a handler to its TheCollectionValueChangedEvent
:
// create the observable collection containing class
MyNotifiableCollectionTestClass collectionTestClass =
new MyNotifiableCollectionTestClass();
// create the handler for event fired when TheCollection
// property is assigned a new value
collectionTestClass.TheCollectionValueChangedEvent +=
CollectionTestClass_TheCollectionValueChangedEvent;
The rest of Program.Main(...)
body is exactly the same as in previous 3 examples.
Here is the CollectionTestClass_TheCollectionValueChangedEvent
handler:
static IDisposable _disposableBehaviors = null;
private static void CollectionTestClass_TheCollectionValueChangedEvent
(
ObservableCollection<MyNotifiablePropsTestClass> collection
)
{
_disposableBehaviors?.Dispose();
// chain the two behaviors - one to record the
// changed property value, the other to maintain
// NumberItemsInCollection of the same size as
// the collection.
_disposableBehaviors = collection
.AddBehavior
(
item => item.PropertyChanged += Item_PropertyChanged,
item => item.PropertyChanged -= Item_PropertyChanged
)
.AddBehavior
(
item => NumberItemsInCollection++,
item => NumberItemsInCollection--
);
}
Note that there is only one disposable token (_disposableBehaviors
) even though we can chain as many behaviors as we want.
Also, note, that we could also use Attach
/Detach
approach outside of the class in which the collection is defined, but we would need to have two events - one indicating that the old collection is removed and one indication that the new collection is created or perhaps one event with two argument - for old and new collections, which is more complex that what we have with disposable token.
When you run this sample, you should get:
Number items in collection: 1
TheString: Item1: Hello World
Number items in collection: 2
TheString: Item2: Hello World
Number items in collection: 1
Number items in collection: 0
Conclusion
In this article, I describe the reusable functionality that absorbs the complexity of creating behaviors for collection items as long as they are part of a collection. When the items are added to a collection, the behaviors are attached to them and when the items are removed from a collection, the behaviors are detached from them. Please enjoy the code!