代码之家  ›  专栏  ›  技术社区  ›  Batuu

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

  •  1
  • Batuu  · 技术社区  · 13 年前

    我正在做一个基于mvvm轻量级工具包的项目。我有一个 MainView DetailsView 用它 相应的ViewModels。两个虚拟机都注册了 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()
        }
    }
    

    这个 控件 使用 ViewModelLocator 以获取现有 DetailsViewModal 作为DataContext。

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

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

    现在的问题是: 因为我想要 控件 作为一个模态窗口,我调用 ShowDialog() 在上面。这似乎挡住了信使,直到 控件 再次关闭。所以 细节视图模式 在模式窗口关闭后接收消息。有什么解决方案可以解决这个问题吗?

    我想如果我能注册 细节视图模式 主视图模型 。这将更改MessageHandler调用的顺序,并且VM更新发生在阻塞之前 显示对话框() 。但是 主视图模型 首先创建并注册,因为它就是这样 细节视图模型 由创建 ViewModalLocator 第一次需要它,所以它总是输掉比赛。

    1 回复  |  直到 13 年前
        1
  •  5
  •   bugged87    13 年前

    不幸的是,我无法再现你的具体问题。我在MainWindowView Loaded事件处理程序中启动了一个单独的线程;一个什么也不做,只持续发送特定消息的线程。然后,我在我的SecondWindowView上调用了ShowDialog(),它的视图模型已注册为侦听此特定消息。第二个窗口的视图模型中的消息处理程序重复执行。事实上,处理程序甚至在调用ShowDailog()之前就已经被调用了,因为我的视图模型已经由ViewModelLocator在应用程序启动时创建了。我需要看到更多的代码来更好地了解您的情况(即,您的服务正在创建详细信息窗口,或者我可以编译以重现问题)。

    您可以尝试以下方法,而不是针对您的孩子窗口。在应用程序中的某个位置定义以下类:

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

    现在创建以下ChildWindowVM类,并以初始化MainWindowVM的方式在ViewModelLocator中对其进行初始化:

    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();
        }
    }
    

    最后一步是将CurrentContent属性从ChildWindowVM类绑定到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属性设置为适当的视图模型,就可以指定要显示的内容。

    实际上还有一件事。如果您确实希望在子窗口中看到任何有用的内容,则需要指定视图和视图模型之间的模板绑定。这可以通过应用程序资源中的xaml来完成:

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

    不要忘记定义名称空间(即“视图模型”和“视图”)。