0
votes

I have read a few articles on here, that describe how to listen to notifications raised. However: I am still having trouble to apply those to my application.

I currently have an application with several "pages".

One of the pages contains a WPF Treeview control in it along with several ViewModels and data models.

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; }
    }
}

This ViewModel contains DriveTreeViewItemViewModels and is bound via DataContext to the UserControl ("page").

The Drive- and DirectoryTreeViewItemViewModel classes contain a few attributes, but are otherwise based on TreeViewItemViewModel, which you can see here:

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
}

I have followed the tutorial and ideas described in http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode and everything works great so far.

My problem is: I would like to add a string "selected" as an attribute to FoldersSearchViewModel, which would contain the path of the selected child ViewModel. The DriveTreeViewItemViewModel and the DirectoryTreeViewItemViewModel each have a "Path" property, that contains the full path to the child.

So: once OnPropertyChanged("IsSelected") is called, I would like to notify FoldersSearchViewModel about it and have the method copy the Path-property from the selected TreeViewItemViewModel into the new "selected"(string) attribute.


I could achieve this by passing the FoldersSearchViewModel-object to the children and children's children etc. in the constructor - but is there no better way of doing this? I suppose I should hook the FoldersSearchViewModel to the PropertyChanged-event of every node and sub-node, but I would like to know what someone with experience in MVVM would do in such a case.

By the way: I could use the WPF Treeview.SelectedItem to get the currently selected TreeViewItemViewModel, but that does not sound right since I want to keep the view, the models and the viewmodels separate.

P.s.: I tried reading and making use of MVVM in WPF - How to alert ViewModel of changes in Model... or should I?, but sadly it does not seem to solve my problem.

Any help is greatly appreciate!

2

2 Answers

0
votes

The way mine works is, the Messenger service is a singleton as discussed. I also use DI, so a VM that needs to use it gets the IMessengerService instance injected into it.

IMessengerService looks like:

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

Message "param" classes are available application wide, so you might have something like:

public class FolderOpened : IMessageBase
{
}

So, the FolderOpened class is available throughout the application, its defined at compile time obviously.

Any client that would care about this message will subscribe to the message in its VM constructor:

_messenger.GetMessage().Handler += ...

It doesn't matter if the sender has "registered" it yet, the messenger is just based on the message class type.

At any time, anybody can send the message:

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

YMMV, but my messenger will automatically disconnect disposed / non existant subscribers, but really, the correct way would be for a VM to unsubscribe in its finalizer or dispose method.

Does that clear it up?

0
votes

The MVVM way to do it would be to use a messenger / event aggregator pattern and broadcast an event.