Proper cleanup of WPF user controls
Asked Answered
R

5

23

I am relatively new to WPF, and some things with it are quite foreign to me. For one, unlike Windows Forms, the WPF control hierarchy does not support IDisposable. In Windows Forms, if a user control used any managed resources, it was very easy to clean up the resources by overriding the Dispose method that every control implemented.

In WPF, the story is not that simple. I have searched for this for several hours, and encountered two basic themes:

The first theme is Microsoft clearly stating that WPF does not implement IDisposable because the WPF controls have no unmanaged resources. While that may be true, they seem to have completely missed the fact that user extensions to their WPF class hierarchy may indeed use managed resources (directly or indirectly through a model). By not implementing IDisposable, Microsoft has effectively removed the only guaranteed mechanism by which unmanaged resources used by a custom WPF control or window can be cleaned up.

Second, I found a few references to Dispatcher.ShutdownStarted. I have tried to use the ShutdownStarted event, but it does not seem to fire for every control. I have a bunch of WPF UserControl's that I have implemented a handler for ShutdownStarted, and it never gets called. I am not sure if it only works for Windows, or perhaps the WPF App class. However it is not properly firing, and I am leaking open PerformanceCounter objects every time the app closes.

Is there a better alternative to cleaning up unmanaged resources than the Dispatcher.ShutdownStarted event? Is there some trick to implementing IDisposable such that Dispose will be called? I would much prefer to avoid using a finalizer if at all possible.

Ratel answered 11/10, 2009 at 8:56 Comment(0)
T
13

I'm afraid that Dispatcher.ShutdownStarted really does seem to be the only mechanism WPF provides for disposing of resources in UserControls. (See a very similar question I asked a while ago).

Another way to approach the problem is to move all of your disposable resources (if at all possible) out of the code behind and into separate classes (such as the ViewModel when using the MVVM pattern). Then at a higher level you could handle your main window closing and notify all the ViewModels via a Messenger class.

I am surprised you don't get the Dispatcher.ShutdownStarted event. Are your UserControls attached to the top-level window at the time?

Threadgill answered 11/10, 2009 at 13:16 Comment(3)
+1 for moving disposable resources out of the codebehind. One of the key learning points for WPF is minimizing the codebehind to take advantage of the strength and expressiveness of the databinding architecture. It's a painful thing to learn (the learning curve is more like climbing a cliff), but rewarding when you "get" the WPF mode of thought.Mekong
All disposable resources are actually in ViewModel's, which are themselves IDisposable. I am really confused about why the Dispatcher.ShutdownStarted event is not firing. The performance counter control (and its associated ViewModel) are indeed attached to the WPF graph as it is embedded in a <Grid> in a <TabControl>.Ratel
@Greg D: I do generally get the WPF model. I started using MVVM as soon as I had grasped the basics of WPF, and my CodeBehind is pretty much as bare as it gets (simply the default constructor and its call to InitializeComponent). The composability and databinding capabilities of WPF are beyond stunning, and I'll never go back to windows forms if I have the choice.Ratel
S
11

The IDisposable interface has (almost) no meaning under WPF, because the mechanism is different from Winforms. In WPF, you must bear in mind the visual and logical tree: that's fundamental.
So, any visual object generally lives as child of some other object. The base of the WPF building mechanism is to attach the visual object hierarchically, then detach and destroy when they aren't useful.

I think you may check the OnVisualParentChanged method exposed since the UIElement: this method is called either when a visual object is attached and when is detached. That could be the right place to dispose the unmanaged objects (sockets, files, etc).

Seventeen answered 11/10, 2009 at 16:5 Comment(1)
Thanks for the tip about OnVisualParentChanged. I'll play with that and see if it helps solve my problem.Ratel
J
9

I was looking for this too and after testing differents options I implemented the solution of venezia

protected override void OnVisualParentChanged(DependencyObject oldParent)
    {
        if (oldParent != null)
        {
            MyOwnDisposeMethod(); //Release all resources here
        }

        base.OnVisualParentChanged(oldParent);
    }

I realized that when parent call Children.Clear() Method and had already items added to Children, DependencyObject had a value. But when parent added an item (Children.Add(CustomControl)) and children was empty DependencyObject was null.

Jampacked answered 28/12, 2012 at 18:36 Comment(1)
I changed it to if (Parent == null) so that if I move the control to a different container, it won't self-destructPoyang
I
0

While others have given you really useful information about this problem, there is a little bit of information that you may not have that will explain a lot about why there is no IDisposable. Basically, WPF (and Silverlight) makes heavy use of WeakReferences - this allows you to reference an object which the GC can still collect.

Insectarium answered 11/10, 2009 at 20:53 Comment(1)
Thanks for the insight Pete. I am curious if you have some links that explain this in more detail? I am curious how heavy use of WeakReferences does not cause problems. They can be a powerful tool in niche situations...but I can't imagine how they are used in WPF.Ratel
P
0

I've got this difficult when I'd used some connection to the database, using some IDbConnection implementation driver or Entity Framework.

I've found in these cases the recommendation is to keep one object connection/context per window, to be able to track changes/transactions. (link)

So I've overridden the OnClosing:

protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
      base.OnClosing(e);
      this._context.Dispose();
}

The context can be my ViewModel/Control which implements IDisposable to clean resources.

Sample use of OnClosing to dispose of resources (link)

Parisparish answered 28/10, 2020 at 14:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.