将子 ViewModel 的更改通知父 ViewModel
Notify parent ViewModel of changes in child ViewModels
我在这里阅读了几篇文章,描述了如何收听发出的通知。但是:我仍然无法将它们应用到我的应用程序中。
我目前有一个包含多个 "pages" 的应用程序。
其中一个页面包含一个 WPF Treeview 控件以及多个 ViewModel 和数据模型。
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; }
}
}
此 ViewModel 包含 DriveTreeViewItemViewModel,并通过 DataContext 绑定到 UserControl ("page")。
Drive- 和 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-属性 从选定的 TreeViewItemViewModel 复制到新的 "selected"(字符串)属性。
我可以通过在构造函数中将 FoldersSearchViewModel 对象传递给子对象和子对象的子对象等来实现此目的 - 但是没有更好的方法吗?我想我应该将 FoldersSearchViewModel 挂接到每个节点和子节点的 PropertyChanged 事件,但我想知道在这种情况下有 MVVM 经验的人会怎么做。
顺便说一下:我可以使用 WPF Treeview.SelectedItem 获取当前选择的 TreeViewItemViewModel,但这听起来不对,因为我想保持视图、模型和视图模型分开。
P.s.: 我尝试阅读和使用 MVVM in WPF - How to alert ViewModel of changes in Model... or should I?,但遗憾的是它似乎并没有解决我的问题。
非常感谢任何帮助!
MVVM 方法是使用信使/事件聚合器模式并广播事件。
我的工作方式是,如前所述,Messenger 服务是一个单例。我也使用 DI,因此需要使用它的 VM 会将 IMessengerService 实例注入其中。
IMessengerService 看起来像:
public interface IMessengerService : IServiceBase
{
Message<T> GetMessage<T>() where T : IMessageBase;
}
消息 "param" classes 在应用程序范围内可用,所以你可能有类似的东西:
public class FolderOpened : IMessageBase
{
}
因此,FolderOpened class 在整个应用程序中可用,显然它是在编译时定义的。
任何关心此消息的客户端都将在其 VM 构造函数中订阅该消息:
_messenger.GetMessage().Handler += ...
发件人是否有 "registered" 并不重要,Messenger 只是基于消息 class 类型。
任何时候,任何人都可以发送消息:
_messenger.GetMessage().SendMessage(...);
YMMV,但我的 Messenger 会自动断开已处理/不存在的订阅者,但实际上,正确的方法是让 VM 在其终结器或处理方法中取消订阅。
这样就清楚了吗?
我在这里阅读了几篇文章,描述了如何收听发出的通知。但是:我仍然无法将它们应用到我的应用程序中。
我目前有一个包含多个 "pages" 的应用程序。
其中一个页面包含一个 WPF Treeview 控件以及多个 ViewModel 和数据模型。
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; }
}
}
此 ViewModel 包含 DriveTreeViewItemViewModel,并通过 DataContext 绑定到 UserControl ("page")。
Drive- 和 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-属性 从选定的 TreeViewItemViewModel 复制到新的 "selected"(字符串)属性。
我可以通过在构造函数中将 FoldersSearchViewModel 对象传递给子对象和子对象的子对象等来实现此目的 - 但是没有更好的方法吗?我想我应该将 FoldersSearchViewModel 挂接到每个节点和子节点的 PropertyChanged 事件,但我想知道在这种情况下有 MVVM 经验的人会怎么做。
顺便说一下:我可以使用 WPF Treeview.SelectedItem 获取当前选择的 TreeViewItemViewModel,但这听起来不对,因为我想保持视图、模型和视图模型分开。
P.s.: 我尝试阅读和使用 MVVM in WPF - How to alert ViewModel of changes in Model... or should I?,但遗憾的是它似乎并没有解决我的问题。
非常感谢任何帮助!
MVVM 方法是使用信使/事件聚合器模式并广播事件。
我的工作方式是,如前所述,Messenger 服务是一个单例。我也使用 DI,因此需要使用它的 VM 会将 IMessengerService 实例注入其中。
IMessengerService 看起来像:
public interface IMessengerService : IServiceBase
{
Message<T> GetMessage<T>() where T : IMessageBase;
}
消息 "param" classes 在应用程序范围内可用,所以你可能有类似的东西:
public class FolderOpened : IMessageBase
{
}
因此,FolderOpened class 在整个应用程序中可用,显然它是在编译时定义的。
任何关心此消息的客户端都将在其 VM 构造函数中订阅该消息:
_messenger.GetMessage().Handler += ...
发件人是否有 "registered" 并不重要,Messenger 只是基于消息 class 类型。
任何时候,任何人都可以发送消息:
_messenger.GetMessage().SendMessage(...);
YMMV,但我的 Messenger 会自动断开已处理/不存在的订阅者,但实际上,正确的方法是让 VM 在其终结器或处理方法中取消订阅。
这样就清楚了吗?