2014-09-17 2 views
2

Я попытался реализовать шаблон посредника в приложении WPF/MVVM, чтобы сделать возможным обмен данными между ViewModels.MVVM + Посредник: Регистрация посредника происходит слишком поздно

Чтобы применить шаблон посредника, я загрузил образец проекта из 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 

Код:

я просто разместить код для ViewModels и моделей сократите длину вопроса.

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 уведомляет, что selectedParentMenuItem изменен до того, как Регистр ChildMenuView зарегистрируется.
  3. Когда вы выбираете любой другой ParentMenuItem, ChildMenuView получает некоторые данные, и он отображает его правильно.
  4. Нажмите на любой дочерний элемент, вы можете ожидать, что страница загружена и какой-то текст в рамке. Но это ничего не отображает. Здесь также я думаю ту же проблему, о которой я упоминал в шаге 2.
  5. Нажмите на любой другой элемент ChildMenuItem. В этот момент Frame должен отображать некоторые данные, а затем приложение работает, как ожидалось.

Итак, мой вопрос: Как уведомить свойство, которое регистрируется после того, как другое свойство вызвало NotifyColleagues?

+9

Ваш вопрос слишком длинный и, вероятно, не получит ответа в его текущей форме. Попробуйте обобщить и включить только соответствующую информацию. Никто не собирается читать ваш вопрос, если потребуется больше 1 или 2 минут ... –

+0

кажется, что проект немного сложнее, мне интересно, зачем вам нужен пользовательский медиатор, когда у вас есть привязки? – pushpraj

+0

@pushpraj вы можете объяснить мне дальше? – Vishal

ответ

5

Найдите обновленную версию своего приложения here.

<Rant> Для меня шаблон медиатора - это просто способ не правильно структурировать код, и я никогда не использовал его в своих реальных сценариях кода. Ваше демонстрационное приложение является ярким примером, когда создание коллекции подмоделей на вашем ViewModel (например, ObservableCollection<ChildMenuViewModel> на ParentMenuViewModel) имеет смысл. Напротив, отслеживание свойства на родительском ViewModel из (даже не существующего) дочернего ViewModel похоже на съемку в ноге. Вместо хорошей иерархии это может быть, это какофония всех, кто вещает. </Rant>.

Если вы действительно хотите остаться внутри этого шаблона, вам необходимо убедиться, что ваш объект зарегистрирован на посреднике (как вы уже заметили в своем вопросе), прежде чем он должен поймать уведомление посредника.

В случае родителя/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" /> 

Для рамы однако, это гораздо сложнее, потому что содержание конкретизируется динамически (упрощенно: путем установки URI в установщике SelectedChildMenuItem). Таким образом, вам нужно, чтобы BindingEngine завершил обновление URI, для загрузки содержимого фрейма и только , затем поднимите свой звонок NotifyColleagues(SelectedChildMenuItem). Это действительно становится уродливым ... Разумеется, есть обоюдный путь для всего, и вы можете обойти худшее, изменив настройку Frame, привязывая Content (см. Выше) вместо Source и создавая экземпляр Контента (SamplePage) перед тем, как сделать звонок NotifyColleagues:

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 
    } 
} 
+0

Отличный материал! Он работает сейчас, как и ожидалось. Я тестировал его и в своем реальном проекте. И это прекрасно работает до сих пор. Я принял ответ и награду за награду через 24 часа в соответствии с правилами stackoverflow.com – Vishal

+0

Если бы вы создали аналогичный проект, то каков будет ваш выбор? Полагаю, не медиатор. Можете ли вы привести пример того, как вы его создадите? – Vishal

+0

Так как вы хорошо просите ... [MenuDemo.zip] (https://drive.google.com/file/d/0BzwIjbsR-mOyYUs1WGRfVkFiRE0/edit?usp=sharing). Единственное, что было немного сложно, - передать DataContext в содержимое фрейма. Обратите внимание, что единственный реальный код слева - это загрузка меню из xml (один раз, а не при каждом изменении выбора). Горизонтальные/VerticalMenu элементы управления заменяют ChildMenuView/ParentMenuView (я стараюсь не иметь несколько просмотров на одном элементе управления, вместо этого у меня есть несколько элементов управления на одном представлении). Помогла ли вам эта помощь, есть ли у вас возражения? –

Смежные вопросы