Disposing WPF User Controls
Asked Answered
S

7

135

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;
Shiff answered 2/2, 2009 at 10:20 Comment(1)
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.Vu
J
62

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

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

Joanniejoao answered 2/2, 2009 at 10:24 Comment(6)
well I was hoping there would be a cleaner way than this, but it looks like for now this is the best to do it.Shiff
But what if the UserControl dies before the app dies? The Dispatcher will only shyt down when the app does, right?Introit
Completely agree, but I don't understand why the OP needs to dispose of controls. Sounds... oddJoanniejoao
Because many controls reuse COM components or other unmanaged resources that were not coded with a view to being left hanging around indefinitely, or finalized on a thread pool thread, and expect/require deterministic deallocation.Acetamide
In a Windows Store App, ShutdownStarted doesn't exist.Pontius
Or you need to dereference event handlers, or you need to stop threads started in that control, ...Messmate
I
52

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
     };
}
Impossibility answered 18/12, 2012 at 9:57 Comment(6)
In a Windows Store App, GetWindow() doesn't exist.Pontius
Bravo, greatest answer.Cm
Cœur: In a Windows Store App, you aren't using WPFKerakerala
what if there are more windows involved and the main one never get closed? Or your control is hosted in page which get loaded/unloaded multiple times ? see: https://mcmap.net/q/168790/-proper-cleanup-of-wpf-user-controlsEyebright
The window might not be closing very often. If the control is part of a list item, many will be created/destroyed until its parent window may close.Kaffir
Note that Closing doesn't always fire when the window closes. Its only called in cases where it makes sense to potentially cancel that close. This is particularly relevant for subwindows; if the window is closing because its Owner window is closing, it will fire Closed but not Closing.Directly
L
16

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.

Link answered 7/7, 2009 at 22:31 Comment(1)
Thank you for this warning. this was my case exactly! Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.InvalidOperationException Stack: at MyControl.Finalize() my solution was to move code from finalizer into ShutdownStartedSissy
F
9

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);
    }
}
Forehanded answered 22/10, 2014 at 9:7 Comment(1)
I'd raise a flag here... what if anything else cancels the window closing (maybe subscribed after your control so e.Cancel is still false when it reaches your WindowClosingHandler delegate)?. Your control would be "unloaded" and the window still opened. I'd definitely do this on the Closed event, not on the Closing one.Phonation
T
6

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!

Thorrlow answered 3/8, 2011 at 20:34 Comment(0)
U
-1

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.

Ultravirus answered 31/5, 2018 at 20:4 Comment(0)
S
-3

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

~MyWpfControl()
    {
        // Dispose of any Disposable items here
    }
Stalker answered 2/2, 2009 at 12:58 Comment(4)
This doesn't seem to work. I just tried that approach and it never gets called.Matador
That's not a destructor, it's a finalizer. You always implement a finalizer and Dispose as a pair otherwise you risk leaks.Mccaslin
And, in finalizer you should only clean up unmanaged objects but not managed objects, because finalizers are run in unspecified order in GC threads thus managed objects may be finalized earlier and their Dispose() may have thread affinity.Scleroprotein
joeduffyblog.com/2005/04/08/… is the best explanation of Finalize and Dispose that I have found. It's really worth reading.Dangerous

© 2022 - 2024 — McMap. All rights reserved.