I'm trying to find a good way of cleaning up unmanaged resources that my custom controls may generate. The scenario is in which the parent window opens a child window that has a custom control with unmanaged resources (see code below). These resources need to be cleaned up when the CustomControl is no longer in use, i.e when the tree it is within is unloaded (i.e the child window closes), or it is removed from a tree (i.e it itself is unloaded)
Method 1 : Unloaded event This gets triggered when you close a child window manually, but not if you close the parent window (which then automatically closes the children)
Method 2 : OnVisualChildrenChanged This doesn't get called when the child window is closed manually or automatically by the parent, and is only of use if the CustomControl is moved to a different parent element.
Method 3 : Dispatcher.ShutdownStarted This isn't really much help as the user may have opened/closed several child windows before they finish with the app, and having that memory only cleaned up at the end isn't good enough.
Method 4 : Have the CustomControl subscribe to ChildWindow.Closing This isn't good enough either, .. the control shouldn't have to know that it is in a window.
Method 5 : Finalizer Suffers from same issue as Method 3, .. it could be a while before its called
public class CustomControlWithManagedResources : Control
{
~CustomControlWithManagedResources()
{
Console.WriteLine("~CustomControlWithManagedResources");
}
public CustomControlWithManagedResources()
{
Unloaded += CustomControl_Unloaded;
Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
}
void Dispatcher_ShutdownStarted(object sender, EventArgs e)
{
Console.WriteLine("ShutdownStarted");
}
void CustomControl_Unloaded(object sender, RoutedEventArgs e)
{
Console.WriteLine("Unloaded");
}
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
if(oldParent != null)
Console.WriteLine("OnVisualParentChanged");
}
}
public class ChildWindow : Window
{
public ChildWindow()
{
Content = new CustomControlWithManagedResources();
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new ChildWindow() { Owner = this }.Show();
}
}
The right way to do this in a WPF application is to use the MVVM pattern and to remove all logic and dependencies from your Views (controls) and into ViewModels.
Your parent ViewModel would create a child ViewModel that implemented IDisposable
and then when it removed the child ViewModel it would call Dispose
on the child ViewModel.
If your main ViewModel has unmanaged resources that need to be cleaned up, then it should implementIDisposable
and the bootstrapper that creates that should take responsibility for cleaning them up.
Another good reference is Caliburn.Micro