我正在开发一个基于mvvm light toolkit的项目。我有一个MainView
和一个DetailsView
相应的ViewModels。两个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()
。这似乎阻止了信使,直到DetailsView
再次关闭。所以DetailsViewModal
在模态窗口关闭后收到消息。有没有解决方法来解决这个问题?
如果我可以在DetailsViewModal
之前注册MainViewModel
,我认为它会起作用。这将改变MessageHandler调用的顺序,并在阻塞ShowDialog()
之前发生VM更新。但是MainViewModel
是先创建并注册的,因为它就是它。 DetailsViewModel
是ViewModalLocator
在第一次需要时创建的,所以它总是失去竞争。
答案 0 :(得分:5)
不幸的是,我无法重现您的具体问题。我在MainWindowView Loaded事件处理程序中触发了一个单独的线程;除了不断发送特定消息之外什么也没做的线程。然后我在我的SecondWindowView上调用ShowDialog(),其视图模型已注册以侦听此特定消息。第二个窗口的视图模型中的消息处理程序重复执行。事实上,甚至在调用ShowDailog()之前就调用了处理程序,因为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();
}
}
最后一步是将ChildWtent属性从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>
不要忘记定义名称空间(即“viewmodels”和“views”)。