如何确保关闭的对话框未从INPC事件中注册? [英] How can I ensure closed dialogs are unregistered from INPC events?
问题描述
我有一个基于MVVM-Light的WPF应用程序,带有一个对话框服务(称为WindowManager
),该服务打开绑定到预先启动的对话框视图模型的对话框窗口,如下所示:
I have an MVVM-Light based WPF application, with a dialog service (called WindowManager
) that opens up dialog windows bound to pre-initiated dialog view-models, like this:
private enum ViewModelKind
{
PlanningGridVM,
InputDialogVM,
TreeViewDialogVM,
SaveFileDialogVM,
MessageBoxVM
}
/// <summary>
/// Shows the Window linked to this ViewModel as a dialog window.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model.</typeparam>
/// <returns>Tri-state boolean dialog response.</returns>
public bool? ShowDialog<TViewModel>(string key = null)
{
ViewModelKind name;
// Attempt to parse the type-parameter to enum
Enum.TryParse(typeof(TViewModel).Name, out name);
Window view = null;
switch (name)
{
// removed some irrelevant cases...
case ViewModelKind.InputDialogVM:
view = new InputDialogView();
System.Diagnostics.Debug.WriteLine(
view.GetHashCode(), "New Window HashCode");
view.Height = 200;
result = view.ShowDialog();
default:
return true;
}
}
对话框的XAML以此开头:
The dialog's XAML starts with this:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:b="clr-namespace:MyCompany.Common.Behaviours"
x:Class="MyCompany.Common.Views.InputDialogView" mc:Ignorable="d"
DataContext="{Binding InputDialogVM, Source={StaticResource Locator}}"
Title="{Binding DisplayName}" MinHeight="200" MinWidth="300" MaxHeight="200"
b:WindowBehaviours.DialogResult="{Binding DialogResult}"
WindowStyle="ToolWindow" ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
Height="200" Width="300">
视图模型在其构造函数中适当地在Messenger中注册,并且它们通过重置视图模型属性来响应初始化消息.这一切都按预期工作.
The view-models appropriately register with Messenger in their constructors, and they respond to initialization messages by resetting the view-model properties. This all works as intended.
为了正确关闭确定/取消"对话框,我有一个名为DialogResult
的附加属性,该属性也可以按预期工作...
In order to properly close my "Okay/Cancel" dialogs, I have an attached property called DialogResult
, which also works as expected...
/// <summary>
/// DialogResult
/// </summary>
public static readonly DependencyProperty DialogResultProperty = DependencyProperty
.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(WindowBehaviours),
new PropertyMetadata(null, DialogResultChanged));
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
private static void DialogResultChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var window = obj as Window;
System.Diagnostics.Debug.WriteLine(
window.GetHashCode(), "Attempting to update DialogResult on Hashcode");
if (window != null && window.IsActive)
{
window.DialogResult = e.NewValue as bool?;
}
}
...但是要注意一点. 您是否注意到我添加了用于跟踪窗口实例的HashCode的Debug输出?它为我确认了以下内容:
...but for one caveat. Did you notice that Debug output I added to track the window instance's HashCode? It confirmed the following for me:
当您有一个可重用的视图模型实例时,对话框视图可以通过XAML中的DataContext绑定访问该对话框,并依次打开一个新对话框几次,这些对话框实例仍保持打开状态,即使在引发了他们的OnClosed事件之后,即使该对话框不再可见!
When you have one reusable view-model instance, accessed by the dialog view through a DataContext binding in the XAML, and you sequentially open up a new dialog several times, those dialog instances remain open, even after their OnClosed event has been raised, and even though the dialog is not visible anymore!
这的净效果是,我必须同时检查窗口的IsActive
属性和检查窗口是否为null.如果不这样做,系统将尝试在仍然存在的每个对话框幻像上设置window.DialogResult
,从而导致System.InvalidOperationException
异常:只有在创建Window并将其显示为对话框之后,才能设置DialogResult."
The nett effect of this is that I have to check the window's IsActive
property in conjunction with checking the window for null. If I don't, the system will try to set window.DialogResult
on every dialog phantom that still remains, resulting in a System.InvalidOperationException
exception: "DialogResult can be set only after Window is created and shown as dialog".
调试输出
New Window HashCode: 4378943
Attempting to update DialogResult on Hashcode: 4378943
New Window HashCode: 53142588
Attempting to update DialogResult on Hashcode: 53142588
New Window HashCode: 47653507
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 47653507
New Window HashCode: 57770831
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 57770831
New Window HashCode: 49455573
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 57770831
Attempting to update DialogResult on Hashcode: 49455573
New Window HashCode: 20133242
Attempting to update DialogResult on Hashcode: 53142588
Attempting to update DialogResult on Hashcode: 57770831
Attempting to update DialogResult on Hashcode: 49455573
Attempting to update DialogResult on Hashcode: 20133242
问题
很多时候,我已经看到它说附加的行为存储了特定于实例的属性的值. 为什么这是相反的方式?
Many times, I have seen it said that an attached behaviour stores the value of the property specific to an instance. Why is this behaving the opposite way?
现在很清楚,那些过期的对话框仍被注册到单个视图模型实例的INPC事件中.如何确保关闭的对话框未从INPC事件中注册?
It is clear now that those expired dialogs are still registered to the single view-model instance's INPC events. How can I ensure closed dialogs are unregistered from INPC events?
推荐答案
感谢Kess向我指出了正确的方向……问题绝不在于销毁对话框实例,而在于确保您拥有每个对话框实例都有一个唯一的视图模型实例!然后,您只需使用内置的Cleanup()
方法从Messenger中注销视图模型.
Thanks to Kess for pointing me in the right direction... the problem was never about destroying a dialog instance, but rather to make sure you have a unique view-model instance for each dialog instance! Then you only have to unregister the view-model from Messenger with the built-in Cleanup()
method.
技巧是使用ServiceLocator
中的GetInstance<T>(string key)
方法并将该密钥传递到您的WindowManager
中.
The trick is to use method, GetInstance<T>(string key)
of ServiceLocator
and to pass that key into your WindowManager
.
InputDialogView XAML:
删除将DataContext分配给ViewModelLocator属性的行
DataContext="{Binding InputDialogVM, Source={StaticResource Locator}}"
WindowManager:
使用传递给ShowDialog的字符串键并使用ServiceLocator(带有该键)来获取唯一的视图模型,并显式设置view.DataContext
public bool? ShowDialog<TViewModel>(string key = null)
{
ViewModelKind name;
Enum.TryParse(typeof(TViewModel).Name, out name);
Window view = null;
switch (name)
{
case ViewModelKind.InputDialogVM:
view = new InputDialogView();
// Added this line
view.DataContext = ServiceLocator.Current
.GetInstance<InputDialogVM>(key);
view.Height = 200;
result = view.ShowDialog();
default:
return true;
}
}
MainViewModel:
使用唯一字符串键通过ServiceLocator
创建新的视图模型
Create a new view-model with ServiceLocator
using a unique string key
internal void ExecuteChangeState()
{
string key = Guid.NewGuid().ToString();
InputDialogVM viewModel = ServiceLocator.Current
.GetInstance<InputDialogVM>(key);
// Use a nested tuple class here to send initialization parameters
var inputDialogParams = new InputDialogVM.InitParams(
"Please provide a reason", "Deactivation Prompt", true);
// This message sends some critical values to all registered VM instances
Messenger.Default.Send(new NotificationMessage<InputDialogVM.InitParams>(
inputDialogParams, CommonMsg.InitInputDialog));
if (_windowManager.ShowDialog<InputDialogVM>(key) == true)
{
var logEntry = new DealsLogEntry()
{
LogDateTime = DateTime.Now,
LogUsername = this.CurrentUser.Username,
Reason = viewModel.InputString
};
_logRepository.Create(logEntry);
}
// Unregister this instance from the Messenger class
viewModel.Cleanup();
}
这篇关于如何确保关闭的对话框未从INPC事件中注册?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!