我尝试在WPF / MVVM应用程序中实现Mediator Pattern,以实现ViewModels之间的通信。
要应用中介模式,我从this link下载了一个示例项目。然后我从样本中学到了它,然后我申请了我的示例项目。
我在使用这种模式时遇到了一些问题,这反过来会产生荒谬的输出。
让我从我的代码开始:
这是我的项目结构:
SampleWPFMVVMMediatorApp
|
|--Data
| |--MenuItems.xml
|
|--Extensions
| |--MediatorX
| | |--IColleague.cs
| | |--Mediator.cs
| | |--Messages.cs
| | |--MultiDictionary.cs
| |--ViewModelBase.cs
|
|--Models
| |--MenuItem.cs
|
|--ViewModels
| |--MainWindowViewModel.cs
| |--ParentMenuViewModel.cs
| |--ChildMenuViewModel.cs
| |--SamplePageViewModel.cs
|
|--Views
| |--ParentMenuView.xaml
| |--ChildMenuView.xaml
| |--SamplePage.xaml
|
|--App.xaml
|--MainWindow.xaml
代码:
我将发布ViewModel和Models的代码以缩短问题的长度。
MenuItem.cs
public class MenuItem
{
public int Id { get; set; }
public string Name { get; set; }
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
Mediator.Register(this, new[] { Messages.SelectedParentMenuItem, Messages.SelectedChildMenuItem });
}
private string _sourcePage;
public string SourcePage
{
get
{
return _sourcePage;
}
set
{
_sourcePage = value;
NotifyPropertyChanged("SourcePage");
}
}
private MenuItem _currentParentMenuItem;
public MenuItem CurrentParentMenuItem
{
get
{
return _currentParentMenuItem;
}
set
{
_currentParentMenuItem = value;
NotifyPropertyChanged("CurrentParentMenuItem");
}
}
private MenuItem _currentChildMenuItem;
public MenuItem CurrentChildMenuItem
{
get
{
return _currentChildMenuItem;
}
set
{
_currentChildMenuItem = value;
NotifyPropertyChanged("CurrentChildMenuItem");
if (CurrentChildMenuItem != null)
{
SourcePage = (from menuItem in XDocument.Load(Messages.DataDirectory + "MenuItems.xml")
.Element("MenuItems").Elements("MenuItem").Elements("MenuItem")
where (int)menuItem.Parent.Attribute("Id") == CurrentParentMenuItem.Id &&
(int)menuItem.Attribute("Id") == CurrentChildMenuItem.Id
select menuItem.Element("SourcePage").Value).FirstOrDefault();
}
}
}
public override void MessageNotification(string message, object args)
{
switch (message)
{
case Messages.SelectedParentMenuItem:
CurrentParentMenuItem = (MenuItem)args;
break;
case Messages.SelectedChildMenuItem:
CurrentChildMenuItem = (MenuItem)args;
break;
}
}
}
ParentMenuViewModel.cs
public class ParentMenuViewModel : ViewModelBase
{
public ParentMenuViewModel()
{
ParentMenuItems = new ObservableCollection<MenuItem>(
from menuItem in XDocument.Load(Messages.DataDirectory + "MenuItems.xml")
.Element("MenuItems").Elements("MenuItem")
select new MenuItem
{
Id = Convert.ToInt32(menuItem.Attribute("Id").Value),
Name = menuItem.Element("Name").Value
}
);
}
private ObservableCollection<MenuItem> _parentMenuItems;
public ObservableCollection<MenuItem> ParentMenuItems
{
get
{
return _parentMenuItems;
}
set
{
_parentMenuItems = value;
NotifyPropertyChanged("ParentMenuItems");
}
}
private MenuItem _selectedParentMenuItem;
public MenuItem SelectedParentMenuItem
{
get
{
return _selectedParentMenuItem;
}
set
{
_selectedParentMenuItem = value;
NotifyPropertyChanged("SelectedParentMenuItem");
Mediator.NotifyColleagues(Messages.SelectedParentMenuItem, SelectedParentMenuItem);
}
}
public override void MessageNotification(string message, object args)
{
throw new NotImplementedException();
}
}
ChildMenuViewModel.cs
public class ChildMenuViewModel : ViewModelBase
{
public ChildMenuViewModel()
{
Mediator.Register(this, new[] { Messages.SelectedParentMenuItem });
}
private MenuItem _currentParentMenuItem;
public MenuItem CurrentParentMenuItem
{
get
{
return _currentParentMenuItem;
}
set
{
_currentParentMenuItem = value;
NotifyPropertyChanged("CurrentParentMenuItem");
ChildMenuItemsOfSelectedParent
= new ObservableCollection<MenuItem>(
from menuItem in XDocument.Load(Messages.DataDirectory + "MenuItems.xml")
.Element("MenuItems").Elements("MenuItem").Elements("MenuItem")
where (int)menuItem.Parent.Attribute("Id") == CurrentParentMenuItem.Id
select new MenuItem
{
Id = Convert.ToInt32(menuItem.Attribute("Id").Value),
Name = menuItem.Element("Name").Value,
}
);
}
}
private ObservableCollection<MenuItem> _childMenuItemsOfSelectedParent;
public ObservableCollection<MenuItem> ChildMenuItemsOfSelectedParent
{
get
{
return _childMenuItemsOfSelectedParent;
}
set
{
_childMenuItemsOfSelectedParent = value;
NotifyPropertyChanged("ChildMenuItemsOfSelectedParent");
}
}
private MenuItem _selectedChildMenuItem;
public MenuItem SelectedChildMenuItem
{
get
{
return _selectedChildMenuItem;
}
set
{
_selectedChildMenuItem = value;
NotifyPropertyChanged("SelectedChildMenuItem");
Mediator.NotifyColleagues(Messages.SelectedChildMenuItem, SelectedChildMenuItem);
}
}
public override void MessageNotification(string message, object args)
{
switch (message)
{
case Messages.SelectedParentMenuItem:
CurrentParentMenuItem = (MenuItem)args;
break;
}
}
}
SamplePageViewModel.cs
public class SamplePageViewModel : ViewModelBase
{
public SamplePageViewModel()
{
Mediator.Register(this, new[] { Messages.SelectedChildMenuItem });
}
private MenuItem _currentChildMenuItem;
public MenuItem CurrentChildMenuItem
{
get
{
return _currentChildMenuItem;
}
set
{
_currentChildMenuItem = value;
NotifyPropertyChanged("CurrentChildMenuItem");
}
}
public override void MessageNotification(string message, object args)
{
switch (message)
{
case Messages.SelectedChildMenuItem:
CurrentChildMenuItem = (MenuItem)args;
break;
}
}
示例:
您可以下载我创建的示例项目here。
问题:
请点击上面提到的链接下载示例项目,以便清楚地了解我的问题。
所以,我的问题是如何在另一家酒店调用NotifyColleagues之后通知一个自我注册的房产?
答案 0 :(得分:5)
查找我的应用here的更新版本。
<Rant>
对我来说,中介模式只是一种不必正确构建代码的方式,而且我从未在我的实际代码场景中使用它。您的演示应用程序是一个很好的例子,在ViewModel上创建子模型集合(例如ObservableCollection<ChildMenuViewModel>
上的ParentMenuViewModel
)非常有意义。相比之下,从(甚至还不存在的)子ViewModel监视父ViewModel上的属性似乎就像在脚下射击自己。它可能不是一个好的层次结构,而是每个人广播的杂音。</Rant>
。
如果你真的想要留在那个模式中,你需要确保你的对象已经注册到Mediator(正如你已经在你的问题中注意到的那样),然后才能抓住Mediator通知。
对于Parent / ChildMenu,这很简单,只需重新排列MainWindow.xaml:
<Grid Grid.Row="1">
<!-- ColumnDefinitions omitted -->
<views:ChildMenuView Grid.Column="0" />
<Frame Grid.Column="1" NavigationUIVisibility="Hidden" Content="{Binding SourcePage}"/>
</Grid>
<views:ParentMenuView Grid.Row="0" />
然而,对于Frame来说,它要复杂得多,因为内容是动态实例化的(简化:通过在SelectedChildMenuItem的setter中设置URI)。因此,您需要BindingEngine完成更新URI,以便加载Frame内容,并且只有然后引发NotifyColleagues(SelectedChildMenuItem)
调用。这真的很丑陋...当然,有一种解决方法,你可以通过更改框架设置环绕最坏的情况,绑定Content
(见上文)而不是Source
和在进行NotifyColleagues调用之前实例化内容(SamplePage
):
private MenuItem _selectedChildMenuItem;
public MenuItem SelectedChildMenuItem
{
get { return _selectedChildMenuItem; }
set
{
_selectedChildMenuItem = value;
NotifyPropertyChanged("SelectedChildMenuItem");
LoadSourcePage(); // first instantiate the page (register it to mediator)
Mediator.NotifyColleagues(Messages.SelectedChildMenuItem, SelectedChildMenuItem); // only now notify
}
}
/// <summary>
/// Get the SourcePage and pass it to MainWindowViewModel
/// </summary>
private void LoadSourcePage()
{
if (SelectedChildMenuItem != null)
{
var sourceUri = (from menuItem in XDocument.Load(Messages.DataDirectory + "MenuItems.xml")
.Element("MenuItems").Elements("MenuItem").Elements("MenuItem")
where (int)menuItem.Parent.Attribute("Id") == CurrentParentMenuItem.Id &&
(int)menuItem.Attribute("Id") == SelectedChildMenuItem.Id
select menuItem.Element("SourcePage").Value).FirstOrDefault();
var relativePart = sourceUri.Substring(sourceUri.IndexOf(",,,") + 3);
var sourcePage = System.Windows.Application.LoadComponent(new Uri(relativePart, UriKind.Relative)); // instantiation with URI
Mediator.NotifyColleagues(Messages.SourcePage, sourcePage); // pass on
}
}