处置WPF用户控件


119

我已经创建了一个供第三方使用的自定义WPF用户控件。我的控件有一个私有成员,该成员是可抛弃的,并且我想确保一旦包含窗口/应用程序关闭,它的dispose方法将始终被调用。但是,UserControl不是一次性的。我尝试实现IDisposable接口并订阅Unloaded事件,但是在主机应用程序关闭时都未调用。如果有可能,我不想依靠控件的使用者记住要调用特定的Dispose方法。

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

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

     }
 }

到目前为止,我发现的唯一解决方案是订阅Dispatcher的ShutdownStarted事件。这是合理的方法吗?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;

用户控件的Unloaded事件呢?
akjoshi 2010年

2
@akjoshi:MSDN表示:卸载事件可能根本不会引发。并且它也可能被触发多次,即当用户更改主题时。
Dudu

尽管可以在用户控件上实现IDisposable接口,但不能保证第三方将调用Dispose模式实现的dispose方法。如果要保留本机资源(例如文件流),则应考虑使用终结器。
菲利普

Answers:


57

有趣的博客文章在这里:

http://geekswithblogs.net/cskardon/archive/2008/06/23/dispose-of-a-wpf-usercontrol-ish.aspx

它提到了订阅Dispatcher.ShutdownStarted来处理您的资源。


1
我本来希望有一种比这更干净的方法,但是现在看来这是最好的方法。
Mark Heath

35
但是,如果UserControl在应用程序死亡之前死亡,该怎么办?分派器只会在应用程序运行时才会回避,对吗?
罗伯特·耶普森

15
因为许多控件会重用未编码的COM组件或其他非托管资源,以使它们无限期地闲逛或在线程池线程上最终确定,并期望/需要确定性释放。
Neutrino

1
在Windows Store应用程序中,ShutdownStarted不存在。
心教堂

7
或者您需要取消引用事件处理程序,或者您需要停止在该控件中启动的线程,...
DanW

40

Dispatcher.ShutdownStarted事件仅在应用程序结束时触发。值得在控件不再使用时调用处理逻辑。特别是在应用程序运行时多次使用控件时,它会释放资源。因此,ioWint的解决方案是更可取的。这是代码:

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
     };
}

在Windows Store应用程序中,GetWindow()不存在。
心教堂

太棒了,最大的答案。
Nic

8
Cur:在Windows Store应用中,您未使用WPF
Alan Baljeu

3
如果涉及更多的窗口而主窗口永不关闭怎么办?还是您的控件托管在多次加载/卸载的页面中?参见:stackoverflow.com/a/14074116/1345207
L.Trabacchin'17年

1
窗口可能不会经常关闭。如果控件是列表项的一部分,则将创建/销毁许多控件,直到其父窗口关闭。
LOST

15

您必须小心使用析构函数。这将在GC Finalizer线程上调用。在某些情况下,您释放的资源可能不喜欢在与其创建时不同的线程上释放。


1
感谢您的警告。这就是我的情况! 应用程序:devenv.exe框架版本:v4.0.30319说明:由于未处理的异常,进程已终止。异常信息:System.InvalidOperationException堆栈:在MyControl.Finalize()处, 我的解决方案是将代码从终结器移到ShutdownStarted中
itsho 2014年

10

我使用以下交互行为向WPF UserControls提供卸载事件。您可以在UserControls XAML中包括该行为。因此,您无需将逻辑放在每个UserControl中就可以使用该功能。

XAML声明:

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

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

CodeBehind处理程序:

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

行为代码:

/// <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);
    }
}

5
我会在这里举一个标志...如果还有其他取消窗口关闭的操作(可能在您的控件后进行了订阅,因此e.Cancel到达您的WindowClosingHandler委托时仍然为false )怎么办?您的控件将被“卸载”,并且窗口仍然打开。我肯定会在Closed活动中这样做,而不是在Closing一个事件中。
Jcl 2014年

6

我的情况几乎没有什么不同,但目的是相同的,我想知道托管用户控件的父窗口何时关闭/关闭,因为视图(即,我的用户控件)应调用演示者oncloseView来执行某些功能并执行清理。(我们正在WPF PRISM应用程序上实现MVP模式)。

我只是想在用户控件的Loaded事件中,可以将ParentWindowClosing方法连接到Parent windows Closing事件。这样,当“父”窗口关闭时,我的Usercontrol可以知道并采取相应的措施!


0

我想在4.7中几乎全部都存在所谓的卸载。但是,如果您正在使用.Net的旧版本,请尝试在加载方法中执行以下操作:

e.Handled = true;

我认为在处理加载之前不会卸载旧版本。刚发布是因为我看到其他人仍然在问这个问题,并且还没有将其作为解决方案。我一年只接触.Net几次,几年前就碰到了。但是,我想知道它是否像在加载完成之前不调用卸载一样简单。似乎它对我有用,但是在较新的.Net中,即使未将加载标记为已处理,它似乎总是调用unload。


-3

一个UserControl有一个析构函数,为什么不使用它呢?

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

这似乎不起作用。我只是尝试了这种方法,却从未被调用过。
JasonD

9
那不是析构函数,而是终结函数。您始终实现终结器,并成对处理,否则有泄漏的风险。
Mike Post,2010年

1
并且,在终结器中,您仅应清除非托管对象,而不是托管对象,因为终结器在GC线程中以未指定的顺序运行,因此托管对象可能会更早完成,并且它们的Dispose()可能具有线程亲和力。
Dudu

2
joeduffyblog.com/2005/04/08/…是我发现的Finalize and Dispose的最佳解释。确实值得一读。
dss539 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.