如何确保从INPC事件中不注册关闭对话框?



我有一个基于MVVM-Light的WPF应用程序,带有一个对话框服务(称为WindowManager),它打开绑定到预先启动的对话框视图模型的对话框窗口,如下所示:

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是这样开始的:

<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注册,它们通过重置视图模型属性来响应初始化消息。

为了正确关闭我的"确定/取消"对话框,我有一个名为DialogResult的附加属性,它也按预期工作…

/// <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?;
    }
}

…但有一点需要注意。你注意到Debug输出我添加跟踪窗口实例的HashCode吗?它为我确认了以下内容:

当你有一个可重用的视图模型实例,由对话框视图通过XAML中的DataContext绑定访问,并且你顺序地打开一个新对话框几次,这些对话框实例保持打开,即使在它们的OnClosed事件被引发之后,即使对话框不再可见!

这样做的最终效果是,我必须检查窗口的IsActive属性,同时检查窗口是否为null。如果我不这样做,系统将尝试在仍然存在的每个对话框幻影上设置window.DialogResult,导致System.InvalidOperationException异常:"只有在创建窗口并显示为对话框后才能设置dialgresult"。


调试输出

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

很多时候,我看到它说附加行为存储特定于实例的属性值。为什么它的行为是相反的?

现在很清楚,那些过期的对话框仍然注册到单个视图模型实例的INPC事件。如何确保关闭对话框从INPC事件中未注册?

感谢Kess为我指出了正确的方向…问题不在于销毁对话框实例,而在于确保每个对话框实例都有一个唯一的视图模型实例!然后,您只需要使用内置的Cleanup()方法从Messenger注销视图模型。

技巧是使用ServiceLocatorGetInstance<T>(string key)方法,并将该密钥传递给WindowManager


<标题> 解决方案

InputDialogView XAML:

删除将DataContext分配给ViewModelLocator属性的行

DataContext="{Binding InputDialogVM, Source={StaticResource Locator}}"

WindowManager:

使用传入ShowDialog的字符串键,并使用ServiceLocator(带键)来获取唯一的视图模型,并显式地设置视图。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的新视图模型
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();
}

相关内容

  • 没有找到相关文章

最新更新