Attached Behavior base like it should have been

by Geert 29. July 2011 12:51

A while ago, I was reading this shocking blog post by Joost van Schaik. It explains that the Behavior.OnDetaching is not always invoked and thus can cause (big) memory issues. He gives a great solution and shows a save way to handle this. He also provides a code snippet to implement this behavior more easily. However, I don’t like writing code twice, even when it is being generated. Therefore, I have decided to continue is excellent work and write a BehaviorBase that completely handles all of this out of the box. In the end, this is what I came up with:

   1: /// <summary>
   2: /// Behavior base class that handles a safe unsubscribe and clean up because the default
   3: /// behavior class does not always call <see cref="Behavior.OnDetaching"/>.
   4: /// <para />
   5: /// This class also adds some specific features such as <see cref="ValidateRequiredProperties"/>
   6: /// which is automatically called when the behavior is attached.
   7: /// </summary>
   8: /// <typeparam name="T">The dependency object this behavior should attach to.</typeparam>
   9: public abstract class BehaviorBase<T> : Behavior<T> where T : FrameworkElement
  10: {
  12:     private bool _isClean = true;

  22:     /// <summary>
  23:     /// Called after the behavior is attached to an AssociatedObject.
  24:     /// </summary>
  25:     /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
  26:     protected sealed override void OnAttached()
  27:     {
  28:         base.OnAttached();
  29:  
  30:         AssociatedObject.Unloaded += OnAssociatedObjectUnloaded;
  31:         _isClean = false;
  32:  
  33:         ValidateRequiredProperties();
  34:  
  35:         Initialize();
  36:     }
  37:  
  38:     /// <summary>
  39:     /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
  40:     /// </summary>
  41:     /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
  42:     protected sealed override void OnDetaching()
  43:     {
  44:         CleanUp();
  45:  
  46:         base.OnDetaching();
  47:     }
  48:  
  49:     /// <summary>
  50:     /// Validates the required properties. This method is called when the object is attached, but before
  51:     /// the <see cref="Initialize"/> is invoked.
  52:     /// </summary>
  53:     protected virtual void ValidateRequiredProperties()
  54:     {
  55:     }
  56:  
  57:     /// <summary>
  58:     /// Initializes the behavior. This method is called instead of the <see cref="OnAttached"/> which is sealed
  59:     /// to protect the additional behavior.
  60:     /// </summary>
  61:     protected virtual void Initialize()
  62:     {
  63:             
  64:     }
  65:  
  66:     /// <summary>
  67:     /// Uninitializes the behavior. This method is called when <see cref="OnDetaching"/> is called, or when the
  68:     /// <see cref="AttachedControl"/> is unloaded.
  69:     /// <para />
  70:     /// If dependency properties are used, it is very important to use <see cref="ClearValue"/> to clear the value
  71:     /// of the dependency properties in this method.
  72:     /// </summary>
  73:     protected virtual void Uninitialize()
  74:     {
  75:             
  76:     }
  77:  
  78:     /// <summary>
  79:     /// Called when the <see cref="AssociatedObject"/> is unloaded.
  80:     /// </summary>
  81:     /// <param name="sender">The sender.</param>
  82:     /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  83:     private void OnAssociatedObjectUnloaded(object sender, EventArgs e)
  84:     {
  85:         CleanUp();
  86:     }
  87:  
  88:     /// <summary>
  89:     /// Actually cleans up the behavior because <see cref="OnDetaching"/> is not always called.
  90:     /// </summary>
  91:     /// <remarks>
  92:     /// This is based on the blog post: http://dotnetbyexample.blogspot.com/2011/04/safe-event-detachment-pattern-for.html.
  93:     /// </remarks>
  94:     private void CleanUp()
  95:     {
  96:         if (_isClean)
  97:         {
  98:             return;
  99:         }
 100:  
 101:         _isClean = true;
 102:  
 103:         if (AssociatedObject != null)
 104:         {
 105:             AssociatedObject.Unloaded -= OnAssociatedObjectUnloaded;
 106:         }
 107:  
 108:         Uninitialize();
 109:     }
 111: }

It looks a lot like the example posted by Joost, but does this different:

  • No need to even think about the negative sides of the Behavior class, just derive from BehaviorBase instead of the original Behavior class and you are good to go.
  • It adds a virtual method ValidateRequiredProperties which is automatically called. I needed this to validate if all required properties were set.
  • So, a derived class (we actually use this in Catel) will look like this:

       1: /// <summary>
       2: /// A <see cref="Behavior"/> implementation for a window. 
       3: /// </summary>
       4: #if SILVERLIGHT
       5: public class WindowBehavior : BehaviorBase<ChildWindow>
       6: #else
       7: public class WindowBehavior : BehaviorBase<Window>
       8: #endif
       9: {
      11:     private WindowLogic _logic;

      18:     /// <summary>
      19:     /// Gets or sets the type of the view model.
      20:     /// </summary>
      21:     /// <value>The type of the view model.</value>
      22:     public Type ViewModelType
      23:     {
      24:         get { return (Type)GetValue(ViewModelTypeProperty); }
      25:         set { SetValue(ViewModelTypeProperty, value); }
      26:     }
      27:  
      28:     /// <summary>
      29:     /// DependencyProperty definition as the backing store for ViewModelType.
      30:     /// </summary>
      31:     public static readonly DependencyProperty ViewModelTypeProperty =
      32:         DependencyProperty.Register("ViewModelType", typeof(Type), typeof(WindowBehavior), new PropertyMetadata(null));

      36:     /// <summary>
      37:     /// Validates the required properties.
      38:     /// </summary>
      39:     protected override void ValidateRequiredProperties()
      40:     {
      41:         if (ViewModelType == null)
      42:         {
      43:             throw new Exception("The 'ViewModelType' must be set when using this behavior");
      44:         }
      45:     }
      46:  
      47:     /// <summary>
      48:     /// Initializes this instance.
      49:     /// </summary>
      50:     protected override void Initialize()
      51:     {
      52:         _logic = new WindowLogic(AssociatedObject, ViewModelType);
      53:     }
      54:  
      55:     /// <summary>
      56:     /// Uninitializes this instance.
      57:     /// </summary>
      58:     protected override void Uninitialize()
      59:     {
      60:         // TODO: Clean up dependency property using ClearValue(MyProperty)
      61:     }
      63: }

    As you can see, it is now extremely easy to create your own safe behaviors with a small bit of additional functionality without having to care how it is saving your from trouble.

    BehaviorBase.cs (3.97 kb) [Downloads: 385]

    Tags: ,

    C# | Catel | Silverlight | Windows Phone 7 | WPF

    Comments (2) -

    peSHIr
    peSHIr Netherlands
    8/4/2011 9:29:29 AM #

    Please loose the hideous artificial #regions in that code. They hurt my eyes! It's bad enough that you apparently use them in your actual code, but that's up to you. In a blog post example like this they have no place whatsoever. Only make the code sample longer. Yuck...

    Geert
    Geert Netherlands
    8/4/2011 9:47:42 AM #

    Should be better now ;)

    Pingbacks and trackbacks (2)+

    Comments are closed

    About the Author

    Geert van Horrik is an independent freelance software developer since January 1st, 2007. Since then he was been working on several projects from C++ to C# (WPF, Silverlight, ASP.NET, etc). Currently he loves to write his software using WPF (or Silverlight if WPF isn't an option).

    Lately, Geert is spending a lot of time on Catel, a free open-source MVVM Framework for WPF and Silverlight. Actually, it's more than "just" an MVVM Framework, it's a complete application library!