Behavior to hide UI elements when a bound collection is empty

3 minute read

screenshot1This is a fun little thingy I wrote when dealing with error lists. Suppose, you want the user to be able to see there are errors, but not directly fly them the whole detailed error list in his face. So there is, for instance a button “Show errors”, like in the application showed to the right.

But, it’s ugly “Show errors” is always visible, even if there are no errors to display. I actually only want to have this button appear when there are errors indeed, so that it acts as an error indicator, and then the user can decide if she wants to see them or not. Of course you can fix that in your viewmodel – subscribe to the events of an ObservableCollection of errors, and turn visibility on or off when trapping those events, every time you need to do this. Perfectly viable solution. But an even better solution is to encapsulate that behavior – the word just says it – in a simple piece of reusable code.

Meet HideWhenCollectionEmptyBehavior. It sports an INotifyCollectionChanged Collection to which you can bind, and the rest of it is actually so simple I am going to show it in one go:

using System.Collections;
using System.Collections.Specialized;
using System.Windows;

namespace Wp7nl.Behaviors
{
  public class HideWhenCollectionEmptyBehavior : SafeBehavior<FrameworkElement>
  {
    protected override void OnSetup()
    {
      base.OnSetup();
      SetVisibility();
    }

    protected override void OnCleanup()
    {
      base.OnCleanup();
      if (Collection != null)
      {
        Collection.CollectionChanged -= OnCollectionChanged;
      }
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      SetVisibility();
    }

    private void SetVisibility()
    {
      var collection = Collection as ICollection;
      AssociatedObject.Visibility = 
        collection != null && collection.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
    }
  }
 }

The behavior is implemented as a SafeBehavior with an attached dependency property “Collection” of type INotifyPropertyChanged to which you can bind the collection that needs to be monitored. The core of the whole behavior is simply the SetVisibility method, which casts the collection to ICollection and checks if it’s null or empty – in that case the Visibility of the object to which this behavior is attached is set to Collapsed – if not, it’s set to Visible.

The attached dependency property is fairly standard, apart from the last part:

#region Collection

public const string CollectionPropertyName = "Collection";

public INotifyCollectionChanged Collection
{
  get { return (INotifyCollectionChanged)GetValue(CollectionProperty); }
  set { SetValue(CollectionProperty, value); }
}

public static readonly DependencyProperty CollectionProperty = DependencyProperty.Register(
    CollectionPropertyName,
    typeof(INotifyCollectionChanged),
    typeof(HideWhenCollectionEmptyBehavior),
    new PropertyMetadata(default(INotifyCollectionChanged), OnCollectionChanged));

public static void OnCollectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var behavior = d as HideWhenCollectionEmptyBehavior;
  var newValue = (INotifyCollectionChanged)e.NewValue;
  var oldValue = (INotifyCollectionChanged)e.OldValue;
  if (behavior != null)
  {
    if (oldValue != null)
    {
      oldValue.CollectionChanged -= behavior.OnCollectionChanged;
    }
    if (newValue != null)
    {
      newValue.CollectionChanged += behavior.OnCollectionChanged;
    }

    behavior.SetVisibility();
  }
}

#endregion

There’s a lot of plumbing going on ‘just to be on the safe side’ – if the bound collection is replaced, the CollectionChanged event is detached from the old collection and attached to the new collection. Everything to prevent memory leaks:-) - but in reality I think only the newValue will ever be set.But anyway, by making the property of type INotifyCollectionChanged, I am sure to have the lowest common denominator and still have a CollectionChanged that I can trap.

By dragging the behavior on top of the “Show Errors”  button and binding to the collections of errors, my app initially looks like displayed on the left. Only when I click “add errors” the “show errors” button appears (courtesy of HideWhenCollectionEmptyBehavior) and then when I click it, I get to see the actual errors.

screenshot3

screenshot1

screenshot2

 

 

 

 

 

 

 

Now of course this is a pretty contrived example, but it is a real-word use case. Like I wrote, I actually use it for indicating that there are errors, but I can think of a lot of other scenarios, for instance an UI element that is displayed when an object has child objects (say, an order has order lines) without actually displaying them – only indicating they are present.

I wrote this behavior to act in a Windows Phone application but the code is so generic it will work in other XAML platforms as well, including Windows 8.

The code of the behavior, together with my beautiful *cough* sample app can be found here

For those who’d like me to use “Any()” in stead of “Count > 0 “ I would like to point out that ICollection is just ICollection, not ICollection<T> and that does not seem to support “Any()”