程序功能
程序使用MVVM模式实现了对WPF TreeView中节点的添加,重命名,删除,上(下)移动,并且可以统计当前TreeView选择的节点和全部接点个数。(截图)
摘要:
- TreeView特点
- 节点的操作源:NodeViewModel
- 掌握TreeView的信息:NodeInfo类
- 你的命令逻辑
- MainViewModel
- View层的主要实现
- MainView
- 原代码下载
- 参考文献
TreeView的特点
无论是哪种UI框架,TreeView都显得比较特殊,WPF中的TreeView亦如此。让我们来简要的看一下TreeView在WPF中的特殊地位: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
MVVM中的ViewModel是数据的最终操作者,而View则反映并且也会影响着ViewModel中数据的结果,本例中至关重要的TreeViewItem的操作者也是数据的代表者:NodeViewModel定义这整个TreeView的节点存储模式。通过在View层的TreeView的DataTemplate在中间做桥梁,两端对象互相影响。
那么NodeViewModel首先具备定义数据的存储形式(一切都是以数据为中心)
那么最原始的NodeViewModel应该至少有如下结构,来反映当前节点的显示名称和子节点。
ObservableCollection
public string Name { get; private set; }
public ReadOnlyObservableCollection
{
get
{
return new ReadOnlyObservableCollection
}
}
其次,一个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
public MainViewModel()
{
Commands = new ReadOnlyCollection
{
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">
MainView
整个ViewModel构建好后,再来讲讲View层,View层应该按照ViewModel的需要来反映(同时也有可能改变ViewModel的数据),注意这个“反映”两个字不仅仅代表者视觉上的输出,还可以执行ViewModel上的命令。另外View可以是任何控件,不过通常是Window或者UserControl
将一些资源元素省去,MainView的XAML结构一目了然
Content="{Binding Commands}"
ContentTemplate="{StaticResource dtCommands}"
DockPanel.Dock="Left"/>
Content="{Binding NodeInfo}"
ContentTemplate="{StaticResource dtNodeInfo}"
DockPanel.Dock="Top"/>
ItemsSource="{Binding Path=Nodes.Children}"
ItemContainerStyle="{StaticResource stTreeViewItem}"/>
最上面是引用的资源。
下面将View的DataContext设置成MainViewModel,
接着DockPanel显示主界面的三大模块:
左面显示绑定的命令,
右上面是NodeInfo信息,
剩下的是主TreeView。
整个MVVM程序完成。
源代码下载
点击下载环境:Microsoft Visual C# 2008 Express Editon
参考文献
WPF Apps With The Model-View-ViewModel Design Patternhttp://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Simplifying the WPF TreeView by Using the ViewModel Pattern
http://www.codeproject.com/KB/WPF/TreeViewWithViewModel.aspx
Working with Checkboxes in the WPF TreeView http://www.codeproject.com/KB/WPF/TreeViewWithCheckBoxes.aspx
How to implement a reusable ICommand http://www.wpftutorial.net/DelegateCommand.html
最新评论