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

时间:2014-09-17 22:05:40

标签: c# wpf mvvm mediator

我尝试在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

问题:

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

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

1 个答案:

答案 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
    }
}