20 April 2011

Safe event detachment ‘pattern’ for behaviors

For the past few weeks I’ve been hacking away at my Windows Phone 7 game and noticed something odd. The objective is to intercept moving objects on the screen and drag/flick them to a target – but as the levels progressed interception and movement became ever more difficult and slow. At least that was reported by my #wp7nl fellow members who were kind (or crazy) enough to test drive the game into the higher levels. I won’t mention names here – yet ;-).

I followed the usual pattern for a behavior – at least, I think it is the usual pattern

using System.Windows;
using System.Windows.Interactivity;

namespace SomeNamespace
{
  public class DemoBehavior : Behavior<FrameworkElement>
  {
    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.Loaded += AssociatedObjectLoaded;
    }

    void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      // Hook up a bunch of events to the AssociatedObject
    }

    protected override void OnDetaching()
    {
      AssociatedObject.Loaded -= AssociatedObjectLoaded;
      // Unhook the other events from the AssociatedObject
      base.OnDetaching();
    }
  }
}

I had seen this problem before while programming in WPF and indeed – when I put a breakpoint in the first line of the OnDetaching  override it was never called. Apparently Windows Phone 7 has the same problem. So here was my game, happily creating a lot of moving objects with each 1-2 behaviors attached to it listening to a bunch of events. They were never unhooked when the objects were deleted. So within a few levels the game started eating resources like there’s no tomorrow.

I modified my behaviors to try to do a general cleanup not only when OnDetaching is called but also when the AssociatedObject is unloaded. This gave me a pattern which I think might be beneficial to everyone who is working with behaviors in general:

using System.Windows;
using System.Windows.Interactivity;

namespace SomeNamespace
{
  public class BetterDemoBehavior : Behavior<FrameworkElement>
  {
    #region Setup
    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.Loaded += AssociatedObjectLoaded;
      AssociatedObject.Unloaded += AssociatedObjectUnloaded;
    }

    void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      // Hook up a bunch of events to the AssociatedObject
    }
    #endregion

    #region Cleanup
    private bool _isCleanedUp;

    private void Cleanup()
    {
      if (!_isCleanedUp)
      {
        _isCleanedUp = true;
        AssociatedObject.Loaded -= AssociatedObjectLoaded;
        AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
        // Unhook the other events from the AssociatedObject
      }
    }

    protected override void OnDetaching()
    {
      Cleanup();
      base.OnDetaching();
    }

    void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
    {
      Cleanup();
    }
    #endregion
  }
}

And because I am not very partial to repetitive work, I created a snippet for this piece of code.

I guess that if both and Windows Phone 7 and WPF have this issue, Silverlight will have it as well. I hope at least this will help people who are, like me, stubborn enough to use Silverlight and MVVMLight to develop games for Windows Phone 7 ;-)

12 comments:

  1. Nice solution. I wonder if you couldn't just use weak references instead.

    ReplyDelete
  2. Ik wacht met spanning of het voorbeeld van het gebruik van weak references op *jouw* blog ;-)

    ReplyDelete
  3. This is interesting. Thanks.
    However I've found out something fishy with this, namely:

    Imagine scenario;

    I have MainWindow, where I have Button which triggers PopupControl.

    Inside PopupControl I have ListBox with items. Now I've attached behavior to each ListBoxItem.

    As soon as the Popup closes, the OnUnloaded is called for behaviors. And the behavior is detached. Pretty neat right?

    Yes. Besides the fact that the behavior wont be re-attached, when opening Popup again.

    Currently I've made IgnoreUnLoadedEvent property to BehaviorBase. For a case like this.

    But maybe you have ideas?

    ReplyDelete
  4. @chris ouch. You have found an unsupported scenario - something I apparently didn't anticipate. I am open to suggestions 😉

    ReplyDelete
  5. I stumbled on the same scenario, though not with a popup menu but with user controls - upon reloading the control the behavior is not attached. So, any ideas so far?

    ReplyDelete
  6. @unknown can you be more specific? Send sample code, for instance?

    ReplyDelete
  7. I've used your idea and creates this Base class, which do the cleaning for you and you just to implement the hook and unhook methods.

    public abstract class BehaviorBase : Behavior where T : FrameworkElement
    {
    #region Setup

    protected override void OnAttached()
    {
    base.OnAttached();
    AssociatedObject.Loaded += AssociatedObjectLoaded;
    AssociatedObject.Unloaded += AssociatedObjectUnloaded;
    }

    private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
    OnAttachedObjectLoaded();

    // Hook up a bunch of events to the AssociatedObject
    OnHookEvents();
    }

    #endregion

    #region Cleanup

    private bool _isCleanedUp;

    private void Cleanup()
    {
    if (!_isCleanedUp)
    {
    _isCleanedUp = true;
    AssociatedObject.Loaded -= AssociatedObjectLoaded;
    AssociatedObject.Unloaded -= AssociatedObjectUnloaded;

    // Unhook the other events from the AssociatedObject
    OnUnHookEvents();
    }
    }

    protected override void OnDetaching()
    {
    Cleanup();

    base.OnDetaching();
    }

    void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
    {
    OnAttachedObjectUnLoaded();

    Cleanup();
    }

    protected virtual void OnAttachedObjectLoaded()
    {
    // if the derived class wants to do something when the AssociatedObject load.
    }

    protected virtual void OnAttachedObjectUnLoaded()
    {
    // if the derived class wants to do something when the AssociatedObject unload.
    }

    protected abstract void OnHookEvents();

    protected abstract void OnUnHookEvents();

    #endregion
    }

    you may find it useful.

    ReplyDelete
  8. @Omar,

    Thanks for thinking with me. But the SafeBehavior base class has been sitting in the Win7nl for some time. Have a look here http://wp7nl.codeplex.com/SourceControl/changeset/view/20956#214132

    ReplyDelete
  9. I realize that this is an old blog post, but I wanted to give some warning regarding the implementation of SafeBehavior: in some cases this is not the desired behavior.

    For example, I have use a particular behavior inside the DataTemplate of an ItemsControl (ListView in this case). It uses a VirtualizingStackPanel and containers are being reused as the UI changes. This results in my AssociatedObject being Unloaded and later Loaded again. However, at that point the behavior has been cleaned up and will no longer function.

    ReplyDelete
  10. @Marcel, I am quite aware of that. Thanks for pointing it out. It has disadvantages, but so far I have more advantages than disadvantages. Using the ListenToPageBackEvent you can program your way around it. In universal apps and later I have dropped the approach

    ReplyDelete
  11. Is there any special reason why you would do the attaching in the Loaded event and not just stick with the existing OnAttached approach?

    ReplyDelete
  12. @DerApe yes - In OnAttached usally not the whole Visual Tree is available. But TBH, this is a very old post. I am not sure how much is still applicable

    ReplyDelete

Note: Only a member of this blog may post a comment.