如何确保关闭的对话框未从INPC事件中注册? [英] How can I ensure closed dialogs are unregistered from INPC events?

查看:124
本文介绍了如何确保关闭的对话框未从INPC事件中注册?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个基于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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆