124
votes

I have created a custom WPF user control which is intended to be used by a third party. My control has a private member which is disposable, and I would like to ensure that its dispose method will always get called once the containing window/application is closed. However, UserControl is not disposable.

I tried implementing the IDisposable interface and subscribing to the Unloaded event but neither get called when the host application closes. MSDN says that the Unloaded event may not be raised at all. And it might also be triggered more than once, that is when user changes theme.

If at all possible, I don't want to rely on consumers of my control remembering to call a specific Dispose method.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

     // where does this code go?
     void Somewhere() 
     {
         if (x != null)
         {
             x.Dispose();
             x = null;
         }

     }
 }

The only solution I have found so far is to subscribe to the Dispatcher's ShutdownStarted event. Is this a reasonable approach?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
7
While you could implement the IDisposable interface on your user control, there is no guarantee that your third party will call the dispose method of your Dispose pattern implementation. If you are holding on to native resources (e.g. a file stream), you should consider using a finalizer.Philippe

7 Answers

59
votes

Interesting blog post here: Dispose of a WPF UserControl (ish)

It mentions subscribing to Dispatcher.ShutdownStarted to dispose of your resources.

45
votes

Dispatcher.ShutdownStarted event is fired only at the end of application. It's worth to call the disposing logic just when control gets out of use. In particular it frees resources when control is used many times during application runtime. So ioWint's solution is preferable. Here's the code:

public MyWpfControl()
{
     InitializeComponent();
     Loaded += (s, e) => { // only at this point the control is ready
         Window.GetWindow(this) // get the parent window
               .Closing += (s1, e1) => Somewhere(); //disposing logic here
     };
}
15
votes

You have to be careful using the destructor. This will get called on the GC Finalizer thread. In some cases the resources that your freeing may not like being released on a different thread from the one they were created on.

9
votes

I use the following Interactivity Behavior to provide an unloading event to WPF UserControls. You can include the behavior in the UserControls XAML. So you can have the functionality without placing the logic it in every single UserControl.

XAML declaration:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<i:Interaction.Behaviors>
    <behaviors:UserControlSupportsUnloadingEventBehavior UserControlClosing="UserControlClosingHandler" />
</i:Interaction.Behaviors>

CodeBehind handler:

private void UserControlClosingHandler(object sender, EventArgs e)
{
    // to unloading stuff here
}

Behavior Code:

/// <summary>
/// This behavior raises an event when the containing window of a <see cref="UserControl"/> is closing.
/// </summary>
public class UserControlSupportsUnloadingEventBehavior : System.Windows.Interactivity.Behavior<UserControl>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += UserControlLoadedHandler;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= UserControlLoadedHandler;
        var window = Window.GetWindow(AssociatedObject);
        if (window != null)
            window.Closing -= WindowClosingHandler;
    }

    /// <summary>
    /// Registers to the containing windows Closing event when the UserControl is loaded.
    /// </summary>
    private void UserControlLoadedHandler(object sender, RoutedEventArgs e)
    {
        var window = Window.GetWindow(AssociatedObject);
        if (window == null)
            throw new Exception(
                "The UserControl {0} is not contained within a Window. The UserControlSupportsUnloadingEventBehavior cannot be used."
                    .FormatWith(AssociatedObject.GetType().Name));

        window.Closing += WindowClosingHandler;
    }

    /// <summary>
    /// The containing window is closing, raise the UserControlClosing event.
    /// </summary>
    private void WindowClosingHandler(object sender, CancelEventArgs e)
    {
        OnUserControlClosing();
    }

    /// <summary>
    /// This event will be raised when the containing window of the associated <see cref="UserControl"/> is closing.
    /// </summary>
    public event EventHandler UserControlClosing;

    protected virtual void OnUserControlClosing()
    {
        var handler = UserControlClosing;
        if (handler != null) 
            handler(this, EventArgs.Empty);
    }
}
6
votes

My scenario is little different, but the intent is same i would like to know when the parent window hosting my user control is closing/closed as The view(i.e my usercontrol) should invoke the presenters oncloseView to execute some functionality and perform clean up. ( well we are implementing a MVP pattern on a WPF PRISM application).

I just figured that in the Loaded event of the usercontrol, i can hook up my ParentWindowClosing method to the Parent windows Closing event. This way my Usercontrol can be aware when the Parent window is being closed and act accordingly!

-1
votes

I'm thinking unload is called all but at hard exist in 4.7. But, if you are playing around with older versions of .Net, try doing this in your loading method:

e.Handled = true;

I don't think older versions will unload until loading is handled. Just posting because I see others still asking this question, and haven't seen this proposed as a solution. I only touch .Net a few times a year, and ran into this a few years back. But, I wonder if it's as simple as unload not being called until loading has finished. Seems like it works for me, but again in newer .Net it seems to always call unload even if loading isn't marked as handled.

-3
votes

An UserControl has a Destructor, why don't you use that?

~MyWpfControl()
    {
        // Dispose of any Disposable items here
    }