处理 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 用户控件的主要内容,如果未能解决你的问题,请参考以下文章