【问题标题】:Disposing WPF User Controls处理 WPF 用户控件
【发布时间】: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 方法。如果你持有原生资源(例如文件流),你应该考虑使用终结器。

标签: c# .net wpf user-controls 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
  • 如果涉及更多窗口而主窗口永远不会关闭怎么办?或者您的控件托管在多次加载/卸载的页面中?见:stackoverflow.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 的最佳解释。真的很值得一读。
      猜你喜欢
      • 2012-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-05
      • 1970-01-01
      • 2016-10-09
      • 2012-04-04
      相关资源
      最近更新 更多