【问题标题】:How to release ViewModel when the View is closed?View 关闭时如何释放 ViewModel?
【发布时间】:2014-05-27 13:25:08
【问题描述】:

我们当前的应用程序将每个View 包装成一个Window(这只是一个原型应用程序)。每个View/Window 都有一个ViewModel 分配为DataContext。现在我的问题是,当Window/View 关闭时,释放ViewModel 的做法是什么?

我尝试处理Window 的关闭事件,并将DataContext 设置为NULL。我的 ViewModelBase 类实现 IDisposable 接口。但是在调试的时候,发现没有调用 Dispose() 函数。当我使用内存分析器时,我看到ViewModel 从未被释放。

那么释放ViewModel的正确做法是什么

更新:

两个潜在的内存泄漏源:

  1. 我正在使用IEventAggregator 进行事件发布/订阅。我正在使用 Prism 4.1 中的 EventAggregator 类,但只是将其包装在一个 Singleton 类中(它实现了 IEventAggregator 接口,因为我们还不想使用依赖注入)。

ViewModel/View 内部,我只使用(VB.NET 中的代码)

_ea.GetEvent(Of MyEventType).Publish(MyEventObject)
_ea.GetEvent(Of MyEventType).Subscribe(AddressOr MyEventHandler)
  1. 我正在使用一些 ActiveX 控件。我正在测试的一个是使用来自 OWC(Office Web 组件)的数据透视表。而且我承认我不知道如何在 MVVM 世界中处理这种控制。我通常有ViewModel直接持有这样一个控件的成员,我认为这违反了MVVM的规则,这也是我目前只能在@987654339的代码隐藏中对ViewModel进行硬编码的原因@。

例如,在代码隐藏中,我有这样一行:

Me.DataContext = New MyViewModel(Me.pTable)

初始化DataContext

而在ViewModel的构造函数中,我通常会做两件事:

a) 设置/更改(在构造函数或函数中)ActiveX 控件的属性,如下所示:

Me.pTable.DisplayDesignTimeUI = False

b) 处理来自ActiveX Control 的事件

AddHandler pTable.DblClick, AddressOf DblClickHandler
AddHandler pTable.SelectionChange, AddressOf PivotTableSelectionChangedHandler

我承认这种“让 ViewModel 处理 ActiveX 控件”的用法对我来说似乎并不好。但这些控制对我们来说是必要的。

【问题讨论】:

  • 您是在代码中将 DataContext 分配为硬引用 (myView.DataContext = new ViewModel();) 还是通过 Binding 提供它?绑定应该允许 ViewModel 被删除,第一个可能有问题。仍然处理 Closing-Event 应该可以工作。任何所有 VM 都使用的单例?
  • 我遇到了同样的问题,并通过让 VM 从指定关闭事件委托的接口继承来解决它。
  • @Gope 对于一些View(以及我正在测试的那个),我确实在代码隐藏中使用了硬引用(最终目标是删除这种做法,但目前我们必须使用它)。是的,我们在ViewModel 中确实有一些单身人士。一些 ServiceLocatorEvengAggregator 我们只是使用 Singleton 实现的。它如何影响参考?
  • @Gope 更糟糕的是,我从内存分析器中看到的根路径可能会阻止内存被释放,它链接到一些 .net ActiveX 控件(VM 作为成员)。这可能是一团糟,但这是我发现处理此类控件的唯一方式
  • @GayotFow 让虚拟机直接处理此类事件是个好主意吗?因为以后可能会改代码所以没有关闭事件/Window,部分地区只有UserControl

标签: wpf mvvm


【解决方案1】:

所以,太多的 cmets。我相信聚合器没有问题。 PRISM 可能会为订阅者使用 Wea​​kReferences,而 VM 在发布时使用静态字段来访问 EventAggregator(对吗?)。这应该没问题,因为 GarbageCollector 确实会收集引用静态对象的实例。

ActiveX-Control 可能更成问题。您是设置许多属性还是仅设置几个?然后您可以创建一个包含 ActiveX-Control 的控件。为您需要执行的每个设置定义 DependencyProperties,并将它们绑定到 viewModel。在 DP 注册中插入 ChangedHandlers。一旦您在 viewModel 中设置了一个值,它就会传输到您的控件,并且您最终会进入该属性的 changehandler,您可以在其中设置 ActiveX-Control 上提交的值(值在 EventArgs.NewValue 中)。

Converter 仅在控件的创建不是那么简单时才有用。然后将 ContentPresenter 放入您的 XAML。将内容绑定到任何内容(将其留空等)并放入返回 ActiveX-Control 实例的转换器 (IValueConverter)。

希望对你有帮助

【讨论】:

  • 谢谢!我想我理解第一种方法(通过定义DependencyProperty 并绑定到ViewModel 中的相应属性来公开ActiveX 控件的属性),即使我们实际上有几十个这样的属性。但在我决定使用这种方法之前,我实际上有一个关键问题:有什么特别的理由让 VM 拥有 ActiveX 控件是一件坏事吗?我的意思是,除了它打破了 MVVM 规则以在 VM 中有一个与视图相关的组件,因此它很难进行单元测试。因为创建这些DependencyProperty 是一项乏味的工作
  • 所以我需要评估是否值得这样做。顺便说一句,我实际上发现了内存泄漏源。我需要在 ActiveX 控件和 WindowsFormsHost 对象中调用 Dispose() 函数来删除事件处理程序。但我仍然对你的提议很感兴趣,因为这种与 VM 的 ActiveX 扭曲一直困扰着我。
  • 介于两者之间:没有人说您不能让一个属性完成所有配置。将 DependencyProperty 的类型设为 KeyValuePair f.e.然后将其设置为 new KVP(MySettings.Height, 10.0)... 就像一个想法
  • 很高兴您发现了泄漏! :) 对于另一个问题:单元测试是恕我直言的最重要原因。 MVVM 规则非常简单:从视图中提取逻辑并放入帮助程序类和模型中(如果需要)。这不是一种模式。这是一种建筑风格,意思是做对你有用的事,但要记住别人是怎么做的,这样你就不会走错方向。
  • 确实如此(让一个属性完成所有配置)!事实上,ActiveX 控件中的大多数属性都不会更改,因此不需要让 VM 为 ActiveX 控件中的每个对应属性公开一个属性。我会接受这种方法
【解决方案2】:

我将发布我对这个问题的直接解决方案。但从长远来看,我认为 Gope 的解决方案更好,因为它符合 MVVM 设计。

Gope 说得对,问题是因为 ActiveX 控件持有对我的 ViewModel 的引用(即使 ViewModel 拥有 ActiveX 控件成员)。我的问题实际上与这篇文章Cleaning up WindowsFormsHost in a WPF dialog 中的相同。我需要调用WindowsFormsHost 对象的Dispose() 函数来释放主机。我还调用了PivotTable.Dispose()来释放ActiveX控件本身(实际上我一开始尝试了PivotTable.Dispose()并释放了PivotTable对象但主机没有。所以我添加了主机的Dispose功能) .现在,当我使用内存分析器进行验证时,似乎我的 ViewModelPivotTable 已正确释放。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    • 2010-10-14
    • 2014-01-14
    • 1970-01-01
    • 2015-07-06
    相关资源
    最近更新 更多