2015-07-22 2 views
0

Я прочитал несколько статей здесь, в которых описывается, как слушать уведомления, поднятые. Однако: у меня все еще есть проблема с тем, чтобы применить их к моей заявке.Уведомить родителя ViewModel изменений в дочернихМодель просмотра

В настоящее время у меня есть приложение с несколькими «страницами».

Одна из страниц содержит элемент управления TreeView WPF, а также несколько моделей ViewModels и данных.

public class FoldersSearchViewModel 
{ 
    private ReadOnlyCollection<DriveTreeViewItemViewModel> _drives; 

    public FoldersSearchViewModel(string[] logicalDrives) 
    { 
     _drives = new ReadOnlyCollection<DriveTreeViewItemViewModel>(
      Environment.GetLogicalDrives() 
      .Select(s => new DriveInfo(s)) 
      .Where(di => di.IsReady) 
      .Select(di => new DriveTreeViewItemViewModel(di)) 
      .ToList() 
     ); 
    } 

    public ReadOnlyCollection<DriveTreeViewItemViewModel> Drives 
    { 
     get { return _drives; } 
    } 
} 

Эта модель представления содержит DriveTreeViewItemViewModels и связанный через DataContext к UserControl ("страницы").

Классы диск- и DirectoryTreeViewItemViewModel содержат несколько атрибутов, но в противном случае на основе TreeViewItemViewModel, который вы можете увидеть здесь:

public class TreeViewItemViewModel : INotifyPropertyChanged 
{ 
    #region Data 

    static readonly protected TreeViewItemViewModel DummyChild = new TreeViewItemViewModel(); 

    readonly ObservableCollection<TreeViewItemViewModel> _children; 
    readonly TreeViewItemViewModel _parent; 

    bool _isExpanded; 
    bool _isSelected; 

    #endregion // Data 

    #region Constructors 

    protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren) 
    { 
     _parent = parent; 

     _children = new ObservableCollection<TreeViewItemViewModel>(); 

     if (lazyLoadChildren) 
      _children.Add(DummyChild); 
    } 

    // This is used to create the DummyChild instance. 
    private TreeViewItemViewModel() 
    { 
    } 

    #endregion // Constructors 

    #region Presentation Members 

    #region Children 

    /// <summary> 
    /// Returns the logical child items of this object. 
    /// </summary> 
    public ObservableCollection<TreeViewItemViewModel> Children 
    { 
     get { return _children; } 
    } 

    #endregion // Children 

    #region HasLoadedChildren 

    /// <summary> 
    /// Returns true if this object's Children have not yet been populated. 
    /// </summary> 
    public bool HasDummyChild 
    { 
     get { return this.Children.Count == 1 && this.Children[0] == DummyChild; } 
    } 

    #endregion // HasLoadedChildren 

    #region IsExpanded 

    /// <summary> 
    /// Gets/sets whether the TreeViewItem 
    /// associated with this object is expanded. 
    /// </summary> 
    public bool IsExpanded 
    { 
     get { return _isExpanded; } 
     set 
     { 
      if (value != _isExpanded) 
      { 
       _isExpanded = value; 
       this.OnPropertyChanged("IsExpanded"); 
      } 

      // Expand all the way up to the root. 
      if (_isExpanded && _parent != null) 
       _parent.IsExpanded = true; 

      // Lazy load the child items, if necessary. 
      if (this.HasDummyChild) 
      { 
       this.Children.Remove(DummyChild); 
       this.LoadChildren(); 
      } 
     } 
    } 

    #endregion // IsExpanded 

    #region IsSelected 

    /// <summary> 
    /// Gets/sets whether the TreeViewItem 
    /// associated with this object is selected. 
    /// </summary> 
    public bool IsSelected 
    { 
     get { return _isSelected; } 
     set 
     { 
      if (value != _isSelected) 
      { 
       _isSelected = value; 
       this.OnPropertyChanged("IsSelected"); 
      } 
     } 
    } 

    #endregion // IsSelected 

    #region LoadChildren 

    /// <summary> 
    /// Invoked when the child items need to be loaded on demand. 
    /// Subclasses can override this to populate the Children collection. 
    /// </summary> 
    protected virtual void LoadChildren() 
    { 
    } 

    #endregion // LoadChildren 

    #region Parent 

    public TreeViewItemViewModel Parent 
    { 
     get { return _parent; } 
    } 

    #endregion // Parent 

    #endregion // Presentation Members 

    #region INotifyPropertyChanged Members 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     if (this.PropertyChanged != null) 
      this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    #endregion // INotifyPropertyChanged Members 
} 

Я последовал за учебник и идеи, описанные в http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode и все прекрасно работает так далеко.

Моя проблема: Я хотел бы добавить строку «selected» в качестве атрибута FoldersSearchViewModel, которая будет содержать путь к выбранному дочернему ViewModel. Модели DriveTreeViewItemViewModel и DirectoryTreeViewItemViewModel имеют свойство Path, содержащее полный путь к дочернему элементу.

Итак: однажды вызывается OnPropertyChanged («IsSelected»), я хотел бы уведомить об этом FoldersSearchViewModel и передать метод Path-property из выбранного TreeViewItemViewModel в новый атрибут «selected» (string).


я мог бы добиться этого, передавая FoldersSearchViewModel-объект к детям и внукам и т.д. в конструкторе - но там нет лучшего способа сделать это? Я предполагаю, что я должен привязать FoldersSearchViewModel к событию PropertyChanged каждого узла и подузла, но я хотел бы знать, что кто-то с опытом работы в MVVM будет делать в таком случае.

К слову: я мог бы использовать WPF Treeview.SelectedItem для получения выбранного в данный момент TreeViewItemViewModel, но это звучит не так, поскольку я хочу, чтобы представление, модели и модели отображались отдельно.

P.s .: Я пробовал читать и использовать MVVM in WPF - How to alert ViewModel of changes in Model... or should I?, но, к сожалению, это не похоже на мою проблему.

Любая помощь очень ценится!

ответ

0

Как работают мои работы, служба Messenger представляет собой одноэлементный, как обсуждалось. Я также использую DI, поэтому виртуальная машина, которая должна ее использовать, получает экземпляр IMessengerService, введенный в него.

IMessengerService выглядит следующим образом:

public interface IMessengerService : IServiceBase 
{ 
    Message<T> GetMessage<T>() where T : IMessageBase; 
} 

сообщений "PARAM" классы широко доступны приложения, так что вы можете иметь что-то вроде:

public class FolderOpened : IMessageBase 
{ 
} 

Итак, класс FolderOpened доступен по всему приложению, его определение было определено во время компиляции.

Любой клиент, который будет заботиться об этом сообщении будет подписываться на сообщение в VM конструктору:.

_messenger.GetMessage() Обработчик + = ...

Это не имеет значения, если отправитель еще «зарегистрировал» его, мессенджер просто основан на типе класса сообщения.

В любое время, любой желающий может отправить сообщение:.

_messenger.GetMessage() SendMessage (...);

YMMV, но мой мессенджер автоматически отключит назначенных/несуществующих подписчиков, но на самом деле правильный способ заключается в том, чтобы виртуальная машина отказалась от подписки в своем финализаторе или методе удаления.

Проясняет это?

+0

Да, это так. Я использую нечто подобное; однако: у меня не было сообщений-объектов, которые я отправлял, но вместо этого я отправил ключ типа propertyName, а также связанный с ним объект. Я верю, что в конечном итоге сообщения, которые реализуют IMessageBase, будут лучше, потому что они могут содержать любое количество отдельных атрибутов. Спасибо. Мне кажется, теперь я понимаю, как заставить его работать (хотя я не использую инъекцию зависимости). – Igor

0

MVVM способ сделать это будет использовать шаблон агрегатора сообщений/событий и транслировать событие.

+0

Hm .. поэтому я должен создать класс Messenger, подписаться на FoldersSearchViewModel для получения уведомлений и чтобы TreeViewItemViewModel уведомил всех подписавшихся участников при изменении события? Проблема, с которой я в настоящее время сталкиваюсь, заключается в следующем: если вызывается TreeViewItemViewModel - где я обрабатываю отмену из класса Messenger? Я прочитал, что метод не называется надежно, что может привести к тому, что в моем приложении будут отсутствовать объекты-объекты или, что еще хуже, утечки памяти. У вас есть предложение? Псевдокод или схема будут очень полезны. – Igor

+0

Вы не регистрируете событие для каждого элемента, вы просто регистрируете общие события. Например, возьмите Explorer. Панель дерева может регистрировать события «SelectionChanged», «ItemOpened» и «ItemClosed» и отправлять класс типа «EventArgs» в качестве параметра (для сообщения). Любой, кто заинтересован в этих событиях, будет подписаться. На самом деле нет необходимости отказаться от подписки на событие в мессенджере, поскольку оно не привязано ни к какому-либо элементу управления, а просто к синглтонному окну. Если вы храните ссылки в обработчике «ItemClosed», вы нарушаете модель :). – SledgeHammer

+0

Хм .. ну EventMessenger действительно синглтон. Он содержит метод NotifySubscribers и передает событие и исходный объект (в настоящее время). Но чтобы узнать, кого уведомить, мне нужно зарегистрировать потенциальных получателей, не так ли? И если мне нужно подписаться на один или несколько событий, мне может потребоваться отменить их в какой-то момент, если ViewModel будет уничтожен - или я ошибаюсь? Есть ли учебник или что-то еще, что бы показать, как именно работает этот шаблон обмена сообщениями? Благодаря! :) – Igor

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