c# - 有什么解决方案可以避免 ShowDialog() 阻塞 Messenger?

标签 c# wpf modal-dialog mvvm-light

我正在开发一个基于 mvvm light 工具包的项目。我有一个 MainView 和一个 DetailsView 及其 相应的 View 模型。两个 VM 都注册了一个 NotificationMessage

// MainViewModel.cs and DetailsViewModel.cs
private void RegisterMessages()
{
    Messenger.Default.Register<NotificationMessage>(this, NotificationMessageHandler);
}

当收到“ShowDetails”消息时,MainViewModel 调用创建“DetailsView”的服务

// MainViewModel.cs
private void NotificationMessageHandler(NotificationMessage msg)
{
    if (msg.Notification == "ShowDetails")
    {
        _detailsService.ShowDetails(); // Does something like (new DetailsView).ShowDialog()
    }
}

DetailsView 使用 ViewModelLocator 获取现有的 DetailsViewModal 作为 DataContext。

DetailsViewModel 应该收到“ShowDetails”消息以更新其内部状态或请求一些数据。

// DetailsViewModel.cs
private void NotificationMessageHandler(NotificationMessage msg)
    {
        if (msg.Notification == "ShowDetails")
        {
            UpdateViewModel();
        }
    }

现在的问题是: 因为我希望 DetailsView 成为模态窗口,所以我对其调用了 ShowDialog()。这似乎会阻止 Messenger,直到 DetailsView 再次关闭。所以 DetailsViewModal 在模态窗口关闭后收到消息。有解决办法吗?

如果我可以在 MainViewModel 之前注册 DetailsViewModal,我认为它会起作用。这将更改 MessageHandler 调用的顺序,并且 VM 更新发生在阻塞 ShowDialog() 之前。但是 MainViewModel 是首先创建和注册的,因为它就是这样。 DetailsViewModel 是在第一次需要时由 ViewModalLocator 创建的,因此它总是输掉比赛。

最佳答案

很遗憾,我无法重现您的具体问题。我在我的 MainWindowView Loaded 事件处理程序中触发了一个单独的线程;一个除了不断发送特定消息之外什么都不做的线程。然后,我在我的 SecondWindowView 上调用了 ShowDialog(),它的 View 模型已注册以收听此特定消息。第二个窗口的 View 模型中的消息处理程序重复执行。事实上,处理程序甚至在调用 ShowDailog() 之前就被调用了,因为我的 View 模型已经在应用程序启动时由 ViewModelLocator 创建。我需要查看更多代码才能更好地了解您的情况(即您正在创建详细信息窗口的服务,或者我可以编译以重现该问题的内容)。

您可以为您的子窗口尝试以下方法。在您的应用程序中的某处定义以下类:

public class ShowChildWindowMessage : MessageBase { }
public class HideChildWindowMessage : MessageBase { }
public class DisplayDetailsMessage : MessageBase { }

现在创建以下 ChildWindowVM 类并在您的 ViewModelLocator 中初始化它,方法与初始化 MainWindowVM 的方式相同:

public class ChildWindowVM : ViewModelBase
{
    private ViewModelBase m_currentContent;
    public ViewModelBase CurrentContent
    {
        get { return m_currentContent; }
        set
        {
            NotifySetProperty(ref m_currentContent, value, () => CurrentContent);
            if (m_currentContent != null)
            {
                m_currentContent.Refresh();
                Messenger.Default.Send(new ShowChildWindowMessage());
            }
        }
    }

    public ChildWindowVM()
    {
        Messenger.Default.Register<DisplayDetailsMessage>(this, OnDisplayDetails);
    }

    private void OnDisplayDetails(DisplayDetailsMessage msg)
    {
        CurrentContent = ViewModelLocator.DetailsViewModel; // or whatever view model you want to display
    }
}

Refresh() 方法将在 DetailsViewModel 类中定义,并负责处理您希望在显示窗口之前执行的任何初始化。请注意,设置 CurrentContent 属性后,将向 MainWindowView 发送一条消息,以创建一个 ChildWindowView 实例,在该实例中显示您的内容。

MainWindowView 代码如下所示:

public partial class MainWindowView : Window
{
    private ChildWindowView m_childWindowView;

    public MainWindowView()
    {
        InitializeComponent();
        Closing += () => ViewModelLocator.CleanUp();      

        Messenger.Default.Register<ShowChildWindowMessage>(this, OnShowChildWindow);
        Messenger.Default.Register<HideChildWindowMessage>(this, OnHideChildWindow);
    }

    private void OnShowChildWindow(ShowChildWindowMessage msg)
    {
        m_childWindowView = new ChildWindowView();
        m_childWindowView.ShowDialog();
    }

    private void OnHideChildWindow(HideChildWindowMessage msg)
    {
        m_childWindowView.Close();
    }
}

最后一步是将 ChildWindowVM 类的 CurrentContent 属性绑定(bind)到您的 ChildWindowView 类。这是在您的 ChildWindowView 的 xaml 中完成的:

<Window x:Class="Garmin.Cartography.AdminBucketTools.ChildWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DataContext="{Binding Path=ChildWindowVm, Source={StaticResource Locator}}">

<Grid>
    <ContentPresenter Content="{Binding Path=CurrentContent}" />
</Grid>

现在您只需调用即可在应用程序的任何位置显示您的详细信息

Messenger.Default.Send(new DisplayDetailsMessage());

您可以通过调用以编程方式关闭窗口

Messenger.Default.Send(new HideChildWindowMessage());

您还可以根据需要从 MessageBase 派生任意数量的类,并在您的 ChildWindowVM 类中注册它们。在每个消息处理程序中,您可以通过将 CurrentContent 属性设置为适当的 View 模型来指定要显示的内容。

事实上,还有一件事。如果你真的想在你的子窗口中看到任何有用的东西,你需要指定你的 View 和 View 模型之间的模板绑定(bind)。这可以通过应用程序资源中的 xaml 完成:

<DataTemplate DataType="{x:Type viewmodels:DetailsViewModel}">
    <views:DetailsView />
</DataTemplate>

不要忘记定义命名空间(即“viewmodels”和“views”)。

关于c# - 有什么解决方案可以避免 ShowDialog() 阻塞 Messenger?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12449511/

相关文章:

c# - Windows 窗体在构造函数完成后立即终止

c# - GDI+ 和系统显卡

wpf - 如何在我的 MVVM 应用程序中利用 MEF?

objective-c - 从 NSDocument 类外部保存 cocoa 文档

c# - 如何制作批处理脚本以使用凭据在远程计算机上运行命令

c# - 如何将 Union 与不同类型的集合一起使用?

jquery - 基于 Ajax 的向导模态框

javascript - 在 div 单击时显示和隐藏弹出窗口

c# - 使用 ObservableCollection 绑定(bind)到 ItemSource 的列表框问题

c# - XAML 解析异常 - xmlns :x ="http://schemas.microsoft.com/winfx/2006/xaml"