I’ve said online in a few different places that attached behaviors and services are your “big guns” when developing WPF applications that follow the ViewModel design pattern. If you run into something you’re finding difficult to program due to the nature of the ViewModel pattern, these are the concepts that will help get you out of the corner you’ve been painted into. I thought I’d write a little series of blog entries that elaborate on this.
The “Little Guns”
First, I’d better step back. I call the attached behavior and services “big guns” for a reason. They should not be your tool of choice on a daily basis. You wouldn’t go duck hunting with a bazooka, nor should you solve most day to day problems with behaviors or services. Most of the day to day problems are solved with the “little guns”, data bindings and commands. We’re only going to pull out the “big guns” when the problem is so large that the “little guns” wouldn’t put a dent in it.
Attached Properties
OK, today we’re just going to discuss attached behaviors. But in order to do so, we need to back up a little bit and discuss attached properties. I suppose, if I were really going to be thorough, I should back all the way up and discuss dependencies properties, but if you’re new enough to WPF to need me to go back that far, you’re not ready for this discussion ;).
An attached property is a dependency property that’s defined on one type, but that can be applied to a dependency object of another type. The typical example given when talking about attached properties are the Grid.Row and Grid.Column properties. These are defined by, and used by, the Grid type, but you set their values on children of the Grid that probably are different types that are entirely unaware of the Grid type and these properties.
<Grid Margin="4">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0"/>
<Button Grid.Row="1" Content="Test"/>
</Grid>
The Remora Pattern
Early in the WPF lifetime, one BenCon blogged about a pattern that he called the Ramora (sic) pattern. I’m not sure if he’s the first to discover this pattern or not, but I’m going to give him credit, as folks are want to do on the intarweb thingy. Over time, this pattern came to be more commonly known as an “attached behavior”, and more recently with the Blend SDK we now just call it a Behavior (though a Behavior is a class that encapsulates this stuff, so don’t let that confuse you). Basically, and attached behavior is just an attached property that when set it applies some behavior to the item it’s attached to, usually by subscribing to some event on the item. If that’s clear as mud to you, hopefully things will become more clear as we actually implement a behavior.
This idea is important to the ViewModel design pattern because often what you’re wanting to do is enable the ViewModel to change the behavior of the View in some way.
Selection
We’re going to look at one of the more common problems you may face when following the ViewModel pattern that’s easily solved by using an attached behavior: tracking and changing the selection in a multi-select ListBox. For single-selection you have a couple of options. The CollectionView has a CurrentItem property that will be the selected item. However, it doesn’t expose a property that represents the selected items. On the ListBox itself the SelectedItem is a dependency property that you can bind to a property on your ViewModel. However, the SelectedItems is a read-only dependency property and can’t be used to track selection directly. The ListBox does have a SelectionChanged event that could be bound to the ViewModel, but that will only provide you with half a solution. You’re ViewModel could use this to track the selection made by the user, but could not use it to programmatically change the selection.
An attached behavior is a perfect solution to this problem. An attached property can be used to bind to a collection exposed by the ViewModel, and the remora pattern can then be used to monitor changes to either the ViewModel’s collection or the ListBox.SelectedItems collection, and apply those changes to the other collection.
public static class Selection
{
private static readonly DependencyProperty SynchronizerProperty =
DependencyProperty.RegisterAttached(
"Synchronizer",
typeof(ListSynchronizer),
typeof(Selection));
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.RegisterAttached(
"Items",
typeof(IList),
typeof(Selection),
new PropertyMetadata(null, OnItemsChanged));
public static IList GetItems(DependencyObject source)
{
return (IList)source.GetValue(ItemsProperty);
}
public static void SetItems(DependencyObject source, IList value)
{
source.SetValue(ItemsProperty, value);
}
private static void OnItemsChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
IList targetList = null;
MultiSelector selector = source as MultiSelector;
if (selector != null)
{
targetList = selector.SelectedItems;
}
else
{
ListBox listBox = source as ListBox;
if (listBox != null)
{
targetList = listBox.SelectedItems;
}
}
if (targetList == null)
{
return;
}
ListSynchronizer synchronizer = (ListSynchronizer)source.GetValue(SynchronizerProperty);
if (synchronizer != null)
{
synchronizer.Dispose();
}
IList sourceList = (IList)e.NewValue;
if (sourceList == null)
{
return;
}
synchronizer = new ListSynchronizer(sourceList, targetList);
}
}
Internally here we’re using a ListSynchronizer type to encapsulate all of the event handling logic, and we’re storing an instance of this type in a private attached dependency property. Here’s the ListSynchronizer in all of its glory.
internal sealed class ListSynchronizer : IDisposable
{
private bool disposed;
private IList source;
private IList target;
public ListSynchronizer(IList source, IList target)
{
this.source = source;
this.target = target;
StartListening(this.source);
StartListening(this.target);
Synchronize();
}
~ListSynchronizer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void StartListening(IList list)
{
INotifyCollectionChanged incc = list as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += OnCollectionChanged;
}
}
private void StopListening(IList list)
{
INotifyCollectionChanged incc = list as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= OnCollectionChanged;
}
}
private void Synchronize()
{
Synchronize(this.target, this.source);
if (!this.target.Cast<object>().SequenceEqual(this.source.Cast<object>()))
{
Synchronize(this.source, this.target);
}
}
private void Synchronize(IList source, IList target)
{
StopListening(target);
try
{
foreach (object item in source)
{
target.Add(item);
}
}
finally
{
StartListening(target);
}
}
private void OnCollectionChanged(
object sender,
NotifyCollectionChangedEventArgs e)
{
IList targetList = object.ReferenceEquals(sender, this.source) ? this.target : this.source;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
PerformActionOnList(targetList, e, Add);
break;
case NotifyCollectionChangedAction.Move:
PerformActionOnList(targetList, e, MoveOrReplace);
break;
case NotifyCollectionChangedAction.Remove:
PerformActionOnList(targetList, e, Remove);
break;
case NotifyCollectionChangedAction.Replace:
PerformActionOnList(targetList, e, MoveOrReplace);
break;
case NotifyCollectionChangedAction.Reset:
Synchronize();
break;
}
}
private void PerformActionOnList(
IList list,
NotifyCollectionChangedEventArgs e,
Action<IList, NotifyCollectionChangedEventArgs> action)
{
StopListening(list);
try
{
action(list, e);
}
finally
{
StartListening(list);
}
}
private void Add(IList list, NotifyCollectionChangedEventArgs e)
{
int itemCount = e.NewItems.Count;
for (int i = 0; i < itemCount; i++)
{
int insertionPoint = e.NewStartingIndex + i;
if (insertionPoint > list.Count)
{
list.Add(e.NewItems[i]);
}
else
{
list.Insert(insertionPoint, e.NewItems[i]);
}
}
}
private void MoveOrReplace(IList list, NotifyCollectionChangedEventArgs e)
{
Remove(list, e);
Add(list, e);
}
private void Remove(IList list, NotifyCollectionChangedEventArgs e)
{
int itemCount = e.OldItems.Count;
for (int i = 0; i < itemCount; i++)
{
list.RemoveAt(e.OldStartingIndex);
}
}
private void Dispose(bool disposing)
{
if (this.disposed)
{
return;
}
if (disposing)
{
StopListening(this.source);
StopListening(this.target);
}
this.disposed = true;
}
}
OK, that code is admittedly a tad complicated, but using it is not.
<ListBox Grid.Row="0"
SelectionMode="Extended"
ItemsSource="{Binding Disciples}"
ui:Selection.Items="{Binding SelectedDisciples}"/>
I should note, similar behaviors have been posted before, including one by fellow Disciple Marlon Gretch. I chose to use this one anyway, because it does the best job of illustrating how a behavior can solve a common ViewModel problem. Besides, I think I was the first person to post such a solution to this problem, though finding it is difficult. I won’t point you there now, because this is a much better implementation and description.
I hope this helps someone new to WPF and the ViewModel pattern. Next up, services!
Edit: Ran across a blog post today by Edward Tanguay with another example, binding to a PasswordBox, where behaviors allow you to work around what would otherwise be a tricky area in the ViewModel pattern (I’ll note that another Disciple Peter O’Hanlon also blogged about a behavior for this, and I’ve had this one in my bag of tricks for quite a while).