处理 WPF 用户控件

Posted

技术标签:

【中文标题】处理 WPF 用户控件【英文标题】:Disposing WPF User Controls 【发布时间】:2010-10-04 21:41:29 【问题描述】:

我创建了一个供第三方使用的自定义 WPF 用户控件。我的控件有一个一次性的私有成员,我想确保一旦包含的窗口/应用程序关闭,它的 dispose 方法将始终被调用。但是,UserControl 不是一次性的。

我尝试实现 IDisposable 接口并订阅 Unloaded 事件,但在主机应用程序关闭时都没有被调用。 MSDN 说可能根本不会引发 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;

【问题讨论】:

虽然您可以在您的用户控件上实现 IDisposable 接口,但不能保证您的第三方会调用您的 Dispose 模式实现的 dispose 方法。如果你持有原生资源(例如文件流),你应该考虑使用终结器。 【参考方案1】:

有趣的博文在这里:Dispose of a WPF UserControl (ish)

它提到订阅Dispatcher.ShutdownStarted 来处置你的资源。

【讨论】:

好吧,我希望有比这更干净的方法,但目前看来这是最好的方法。 但是如果 UserControl 在应用死掉之前就死掉了怎么办? Dispatcher 只会在应用程序运行时关闭,对吧? 因为许多控件重用 COM 组件或其他未编码的非托管资源,以便无限期地闲置,或在线程池线程上完成,并且期望/需要确定性解除分配。 在 Windows 应用商店应用中,ShutdownStarted 不存在。 或者您需要取消引用事件处理程序,或者您需要停止在该控件中启动的线程,...【参考方案2】:

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 应用商店应用中,GetWindow() 不存在。 太棒了,最好的答案。 Cœur:在 Windows 应用商店应用中,您没有使用 WPF 如果涉及更多窗口而主窗口永远不会关闭怎么办?或者您的控件托管在多次加载/卸载的页面中?见:***.com/a/14074116/1345207 窗口可能不会经常关闭。如果控件是列表项的一部分,则会创建/销毁许多控件,直到其父窗口可能关闭。【参考方案3】:

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

【讨论】:

感谢您的警告。这正是我的情况! 应用程序:devenv.exe 框架版本:v4.0.30319 描述:进程因未处理的异常而终止。异常信息:System.InvalidOperationException 堆栈:在 MyControl.Finalize() 我的解决方案是将代码从终结器移动到 ShutdownStarted【参考方案4】:

我使用以下交互行为向 WPF 用户控件提供卸载事件。 您可以在 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);
    

【讨论】:

我会在这里举个旗子...如果有其他任何事情取消了窗口关闭(可能在您的控制之后订阅,所以e.Cancel 在到达您的WindowClosingHandler 代表时仍然是错误的)?您的控件将被“卸载”并且窗口仍然打开。我肯定会在 Closed 事件中这样做,而不是在 Closing 事件中。【参考方案5】:

我的场景略有不同,但意图是相同的清理。 (我们正在 WPF PRISM 应用程序上实现 MVP 模式)。

我刚刚发现在用户控件的 Loaded 事件中,我可以将 ParentWindowClosing 方法连接到 Parent windows Closing 事件。这样我的用户控件就可以知道父窗口何时关闭并采取相应的行动!

【讨论】:

【参考方案6】:

我认为卸载在 4.7 中几乎不存在。但是,如果您正在使用旧版本的 .Net,请尝试在您的加载方法中执行此操作:

e.Handled = true;

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

【讨论】:

【参考方案7】:

一个用户控件有一个析构函数,你为什么不使用它?

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

【讨论】:

这似乎不起作用。我只是尝试了这种方法,但它永远不会被调用。 这不是析构函数,它是终结器。您始终将终结器和 Dispose 实现为一对,否则您将面临泄漏的风险。 并且,在终结器中,您应该只清理非托管对象而不是托管对象,因为终结器在 GC 线程中以未指定的顺序运行,因此托管对象可能更早完成,并且它们的 Dispose() 可能具有线程亲和性. joeduffyblog.com/2005/04/08/… 是我发现的对 Finalize 和 Dispose 的最佳解释。真的很值得一读。

以上是关于处理 WPF 用户控件的主要内容,如果未能解决你的问题,请参考以下文章

是否有任何 WPF 控件可以处理在窗口中导航剪辑的菜单或用户界面?

WPF,从用户控件中更新主窗口中的状态栏

WPF用户控件嵌套控件-从用户控件切换用户控件

WPF自定义控件の用户控件(完结)

请教wpf中的用户控件,如何模拟出它的close事件

WPF路由