MVVM + Mediator模式:Mediator的注册发生得太晚了

Vis*_*hal 2 c# wpf mvvm mediator

我试图在WPF/MVVM应用程序中实现Mediator Pattern,以实现ViewModels之间的通信.

要应用中介模式,我从此链接下载了一个示例项目.然后我从样本中学到了它,然后我申请了我的示例项目.

我对这种模式的使用有一些问题,这反过来会产生荒谬的输出.

让我从我的代码开始:

这是我的项目结构:

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
Run Code Online (Sandbox Code Playgroud)

码:

我将发布ViewModels和Models的代码以缩短问题的长度.

MenuItem.cs

public class MenuItem 
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

样品:

您可以下载我在此处创建的示例项目.

问题:

请点击上面提到的链接下载示例项目,以清楚地了解我的问题.

  1. 运行该应用程序.
  2. 正如您可能希望ChildMenuView显示某些项目,它最初不会显示任何内容.我认为发生此问题是因为ParentMenuView通知在ChildMenuView注册自身之前已更改selectedParentMenuItem.
  3. 当您选择任何其他ParentMenuItem时,ChildMenuView会获取一些数据并正确显示它.
  4. 单击任何childMenuItem,您可能希望在Frame上看到加载的页面和一些文本.但这并没有显示任何东西.在这里我也想到了我在step2中提到的同样的问题.
  5. 单击任何其他ChildMenuItem.这次Frame应显示一些数据,然后app按预期工作.

所以,我的问题是如何在另一家酒店打电话给NotifyColleagues后通知自己注册的房产?

Mik*_*chs 5

在此处查找我的应用程序的更新版本.

<Rant>对我来说,中介模式只是一种不必正确构建代码的方式,而且我从未在我的实际代码场景中使用它.您的演示应用程序是一个很好的例子,在ViewModel上创建一个子模型集合(例如ObservableCollection<ChildMenuViewModel>on 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" />
Run Code Online (Sandbox Code Playgroud)

然而,对于Frame来说,它要复杂得多,因为内容是动态实例化的(简化:通过在SelectedChildMenuItem的setter中设置URI).所以,你需要为BindingEngine以完成更新的URI,为框架内容加载的,只有提高你的NotifyColleagues(SelectedChildMenuItem)电话.这真的变得丑陋......当然,有一种解决方法,你可以通过改变你的Frame设置来环绕最坏的情况,绑定Content(见上文)而不是在实现NotifyColleagues调用之前Source实例化Content(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
    }
}
Run Code Online (Sandbox Code Playgroud)