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)
{
MyNotifiablePropsTestClass myTestClass = new MyNotifiablePropsTestClass();
PrintNotifiablePropertyBehavior printNotifiablePropertyBehavior =
new PrintNotifiablePropertyBehavior();
printNotifiablePropertyBehavior.Attach(myTestClass);
myTestClass.TheString = "Hello World";
printNotifiablePropertyBehavior.Detach(myTestClass);
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:
printNotifiablePropertyBehavior.Detach(myTestClass);
and change TheString
property to "Bye World
":
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>
{
private static void NotifyiableObject_PropertyChanged
(
object sender,
PropertyChangedEventArgs e
)
{
sender.PrintPropValue(e.PropertyName);
}
public void Attach(INotifyPropertyChanged notifyiableObject)
{
notifyiableObject.PropertyChanged +=
NotifyiableObject_PropertyChanged;
}
public void Detach(INotifyPropertyChanged notifyiableObject)
{
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):
MyNotifiableCollectionTestClass collectionTestClass =
new MyNotifiableCollectionTestClass();
MyNotifiablePropsTestClass item1 = new MyNotifiablePropsTestClass();
collectionTestClass.TheCollection =
new ObservableCollection<MyNotifiablePropsTestClass>
(
new MyNotifiablePropsTestClass[] { item1 }
);
item1.TheString = "Item1: Hello World";
MyNotifiablePropsTestClass item2 = new MyNotifiablePropsTestClass();
collectionTestClass.TheCollection.Add(item2);
item2.TheString = "Item2: Hello World";
collectionTestClass.TheCollection.RemoveAt(1);
item2.TheString = "Item2: Bye Wordl";
collectionTestClass.TheCollection = null;
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:
void UnsetItems(IEnumerable items)
{
if (items == null)
return;
foreach (MyNotifiablePropsTestClass item in items)
{
item.PropertyChanged -= Item_PropertyChanged;
}
}
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
)
{
UnsetItems(e.OldItems);
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)
{
_collection.CollectionChanged -=
_collection_CollectionChanged;
}
UnsetItems(this._collection);
this._collection = value;
SetItems(this._collection);
if (_collection != null)
{
_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:
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;
}
_doForEachItemCollectionBehavior.Detach(_collection);
this._collection = value;
_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();
_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;
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;
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
:
MyNotifiableCollectionTestClass collectionTestClass =
new MyNotifiableCollectionTestClass();
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();
_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!