wpfmvvm,WPF中使用MVVM模式操作TreeView

程序功能

程序使用MVVM模式实现了对WPF TreeView中节点的添加,重命名,删除,上(下)移动,并且可以统计当前TreeView选择的节点和全部接点个数。
(截图)
imageWPF中使用MVVM模式操作TreeViewwpfmvvm
摘要:
  • TreeView特点
  • 节点的操作源:NodeViewModel
  • 掌握TreeView的信息:NodeInfo类
  • 你的命令逻辑
  • MainViewModel
  • View层的主要实现
  • MainView
  • 原代码下载
  • 参考文献

TreeView的特点

无论是哪种UI框架,TreeView都显得比较特殊,WPF中的TreeView亦如此。让我们来简要的看一下TreeView在WPF中的特殊地位:
imageimageWPF中使用MVVM模式操作TreeViewwpfmvvm
1. 理论上讲,TreeView也属于选择器,但由于其特殊的存储结构WPF中的TreeView并没有像ListBox,TabControl等控件一样继承与Selector类,而是直接继承自ItemsControl类,并定义自己的SelectedItem,SelectedValue等Selector所有的属性。
2. TreeView使用HierarchicalDataTemplate和TreeViewItem(继承自HeaderedItemsControl)。
3. 没有对CollectionView的支持,因此也没有IsSynchronizedWithCurrentItem属性。
在CodeProject上看到一篇非常好的文章(对我帮助很大):Simplifying the WPF TreeView by Using the ViewModel Pattern,地址:http://www.codeproject.com/KB/WPF/TreeViewWithViewModel.aspx
不过这篇文章没有讲节点的添加删除移动操作和信息的统计,希望大家都看看这篇非常出色的文章。

节点的操作源:NodeViewModel

imageimageimageWPF中使用MVVM模式操作TreeViewwpfmvvm
MVVM中的ViewModel是数据的最终操作者,而View则反映并且也会影响着ViewModel中数据的结果,本例中至关重要的TreeViewItem的操作者也是数据的代表者:NodeViewModel定义这整个TreeView的节点存储模式。通过在View层的TreeView的DataTemplate在中间做桥梁,两端对象互相影响。
那么NodeViewModel首先具备定义数据的存储形式(一切都是以数据为中心)
那么最原始的NodeViewModel应该至少有如下结构,来反映当前节点的显示名称和子节点。
ObservableCollection children;

public string Name { get; private set; }
public ReadOnlyObservableCollection Children
{
get
{
return new ReadOnlyObservableCollection(children);
}
}

其次,一个ViewModel不仅仅是用来存储View的数据源,还需要有其他数据,用来影响View的特性或者影响自己的操作模式,这些大家都懂,那么就这个例子来说,我们需要定义节点是否被选择,是否展开,以及一些相应事件和操作。并且注意需要继承INotifyPropertyChanged来反映属性变化,用ObservableCollection对象来反映容器变化。两者分别在System.ComponentModel和System.Collections.ObjectModel命名空间内。
NodeViewModel的事件部分
#region 事件

public event EventHandler IsExpandedChanged;
public event EventHandler IsSelectedChanged;

protected virtual void _disibledevent=> {
if (IsExpandedChanged != null)
IsExpandedChanged(this, EventArgs.Empty);
}

protected virtual void _disibledevent=> {
if (IsSelectedChanged != null)
IsSelectedChanged(this, EventArgs.Empty);
if (IsSelected)
NodeInfo.SelectedNode = this;
}

#endregion

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void _disibledevent=>string proName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(proName));
}

#endregion


定义好事件后要在相应属性改编后,或者函数操作中对触发事件,这样依赖于事件的另一方代码才会被正确执行。
NodeViewModel的节点操作代码就是基本的容器操作,实现起来比较简单,这里就不再多说了。

掌握TreeView的信息:NodeInfo类

那么现在如果NodeViewModel构建好后,如果我们要在ViewModel层上知道TreeView的当前选择节点和全部节点个数,该怎么办?
我们在定义NodeViewModel时就说过要额外定义一些其他特性比如节点的选择控制,并且View和ViewModel可以通过一些桥梁而互相影响(比如绑定),这是每一个节点在View中是否被选择会立即反映到ViewModel中,问题是怎样知道当前那个被选择的节点?此时,我们之前定义的事件有了用处,即利用每个节点被选择后发出的事件,我们把这个节点信息反馈到一个公共的地方,这个公共的地方就是:NodeInfo类。
节点个数的统计原理也一样,每当节点个数发生变化时,我们都会对总的节点个数进行相应的更新。
NodeInfo类代码(部分)
class NodeInfo : INotifyPropertyChanged
{
NodeViewModel selectedNode;
int count;

public NodeViewModel SelectedNode
{
get { return selectedNode; }
set
{
if (selectedNode != value)
{
selectedNode = value;
OnSelectedNodeChanged();
OnPropertyChanged("SelectedNode");
}
}
}

public int Count
{
get { return count; }
private set
{
if (count != value)
{
count = value;
OnPropertyChanged("Count");
}
}
}

internal void SetCount(int newcount)
{
Count = newcount;
}

public event EventHandler SelectedNodeChanged;

protected virtual void _disibledevent=> {
if (SelectedNodeChanged != null)
SelectedNodeChanged(this, EventArgs.Empty);
}
}


最后在每一个NodeViewModel中加入这个统一的NodeInfo,NodeInfo就成为节点操作后统计信息的公共场所,最终的NodeInfo会被存放着MainViewModel中(后面会讲MainViewModel)。

你的命令逻辑

这个子标题是“你的命令逻辑”,什么意思?就是说你可以按照自己的方式在ViewModel中定义命令。你可以直接写一个继承ICommand的类,或者用其他MVVM框架中常见的类,比如Josh Smith的RelayCommand或者Prism中的DelegateCommand。
我们就简简单单用DelegateCommand并根据需要另外定义DisplayName代表命令的显示名称,和HasCanExecuted来判断命令是否需要刷新,如果是则调用DelegateCommand的RaiseCanExecuteChanged方法。

参考:
RelayCommand:http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
DelegateCommand:http://msdn.microsoft.com/en-us/library/ff654132.aspx


MainViewModel

确定好了命令逻辑实现,我们就可以在MainViewModel定义最终的命令,在构造函数中初始化所有命令并将他们存在一个容器中,在需要更新的时候对相应命令进行更新。
同时,我们在这里初始化NodeInfo和NodeViewModel。这样程序在ViewModel层中的所有操作就完成了。
MainViewModel
class MainViewModel : INotifyPropertyChanged
{
public NodeViewModel Nodes { get; private set; }
public NodeInfo NodeInfo { get; private set; }
public ReadOnlyCollection Commands { get; private set; }

public MainViewModel()
{
Commands = new ReadOnlyCollection(new DelegateCommand[]
{
new DelegateCommand(p=>GetOperationNode().Add(NodeViewModel.GetNextDataName()), "在开头添加"),
new DelegateCommand(p=>GetOperationNode().Append(NodeViewModel.GetNextDataName()), "在结尾添加"),
new DelegateCommand(p=>NodeInfo.SelectedNode.Rename(),CheckSelection,"重命名"),
new DelegateCommand(p=>NodeInfo.SelectedNode.Remove(), CheckSelection,"删除"),
new DelegateCommand(p=>NodeInfo.SelectedNode.MoveUp(), CheckSelection,"上移"),
new DelegateCommand(p=>NodeInfo.SelectedNode.MoveDown(), CheckSelection,"下移")
});

NodeInfo = new NodeInfo();
NodeInfo.SelectedNodeChanged += (s, e) => RefreshCommands();
Nodes = new NodeViewModel(NodeInfo);
}

NodeViewModel GetOperationNode()
{
if (NodeInfo.SelectedNode == null)
return Nodes;
return NodeInfo.SelectedNode;
}

bool CheckSelection(object obj)
{
return NodeInfo.SelectedNode != null;
}

void RefreshCommands()
{
foreach (var cmd in Commands)
if (cmd.HasCanExecuted)
cmd.RaiseCanExecuteChanged();
}



#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void _disibledevent=>string proName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(proName));
}

#endregion
}



View层的主要实现

TreeView的DataTemplate和TreeViewItem设置







命令的绑定


HorizontalContentAlignment="Stretch"
Background="Transparent"
Margin="10,3">


最新评论

发表评论