Silverlight TreeView MVVM 绑定实现
在WPF/Silverlight开发中,我们都推荐使用MVVM模式进行开发,便于业务与UI的分离和单元测试。但在Silverlight中对TreeView的处理涉及到对TreeViewItem的相关操作如果用MVVM来实现的话,还是不是那么容易的。因为在微软提供的TreeView控件中并没有包含可以直接对TreeViewItem操作的Attach事件。而且在采用数据绑定的方式下每个TreeViewItem是在根据数据模板的层级关系来自动生成的,因此我们要获取每个TreeViewItem也不是那么容易的。
这里模拟一个需求场景:在MVVM模式下根据对TreeView节点的展开或者关闭来实现节点图标的修改(类似资源管理器中的目录结构)。那么如何来实现呢?
分析需求:根据要求,我们知道需要处理的是每个TreeViewItem的Expanded或者Collapsed事件,但是根据前面的描述我们知道在Silverlight的TreeView中并不能直接对TreeViewItem的展开或者关闭事件进行处理。那么我们如何来才能捕获到这两个事件呢?
在这里,我们只能够对TreeView控件和TreeViewItem控件进行扩展处理。我们可以自定义两个类,一个Tree,一个TreeItem。它们分别继承只TreeView和TreeViewItem类。在它们中我们重新定义两个路由事件:public new event RoutedEventHandler Expanded; public new event RoutedEventHandler Collapsed;并且重写它们的GetContainerForItemOverride()方法。TreeView的每个孩子节点就是TreeViewItem以及TreeViewItem的子节点的都能够处理这两个事件。这里有点拗...直接上代码大家就明白了:
public class TreeItem : TreeViewItem { public new event RoutedEventHandler Expanded; public new event RoutedEventHandler Collapsed; protected override DependencyObject GetContainerForItemOverride() { TreeItem item = new TreeItem(); item.Expanded += (s, e) => this.RaiseEvent(this.Expanded, s, e); item.Collapsed += (s, e) => this.RaiseEvent(this.Collapsed, s, e); return item; } protected override void OnExpanded(RoutedEventArgs e) { this.RaiseEvent(this.Expanded, this, e); base.OnExpanded(e); } protected override void OnCollapsed(RoutedEventArgs e) { this.RaiseEvent(this.Collapsed, this, e); base.OnCollapsed(e); } private void RaiseEvent(RoutedEventHandler handler, object sender, RoutedEventArgs e) { if (handler != null) { handler.Invoke(sender, e); } } }
public class Tree : TreeView { public event RoutedEventHandler Expanded; public event RoutedEventHandler Collapsed; protected override DependencyObject GetContainerForItemOverride() { TreeItem item = new TreeItem(); item.Expanded += (s, e) => this.RaiseEvent(this.Expanded, s, e); item.Collapsed += (s, e) => this.RaiseEvent(this.Collapsed, s, e); return item; } void RaiseEvent(RoutedEventHandler handler, object sender, RoutedEventArgs e) { if (handler != null) handler.Invoke(sender, e); } }
这里我们的目的是使每个TreeViewItem在触发Expanded和Collapsed事件的时候能够通知到Tree的Expanded和Collapsed事件。其实这里我们已经实现了能够通过绑定来处理这两个事件了,但是我们仔细研究会发现,处理的时候并不能很好的处理到TreeViewItem对应的数据项。肿么办???
其实这里我们只需要自定义两个命令行为(继承自 CommandBehaviorBase<T>)就可以达到这个目的了。PS:我使用的是Silverlight的Prism框架,CommandBehaviorBase<T>需要添加Microsoft.Practices.Prism.dll的引用。这两个命令行为分别处理Tree对应的Expanded事件以及Collapsed事件,在这两个事件中将TreeViewItem的DataContext作为参数传递到后台处理。这里提供Expanded行为的实现,Collapsed行为的实现类似,不再重复。
public class TreeExpandedBehavior : CommandBehaviorBase<Tree> { public TreeExpandedBehavior(Tree targetObject) : base(targetObject) { targetObject.Expanded += new RoutedEventHandler(targetObject_Expanded); } void targetObject_Expanded(object sender, RoutedEventArgs e) { base.CommandParameter = ((FrameworkElement)sender).DataContext; base.ExecuteCommand(); } } public static class TreeExpanded { public static readonly DependencyProperty TreeExpandedBehaviorProperty = DependencyProperty.RegisterAttached("TreeExpandedBehaviorProperty", typeof(TreeExpandedBehavior), typeof(TreeExpanded), null); public static ICommand GetCommand(DependencyObject obj) { return (ICommand)obj.GetValue(CommandProperty); } public static void SetCommand(DependencyObject obj, ICommand value) { obj.SetValue(CommandProperty, value); } public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(TreeExpanded), new PropertyMetadata((s, e) => { var tree = s as Tree; if (tree != null) { var behavior = GetOrCreateBehavior(tree); behavior.Command = e.NewValue as ICommand; } })); private static TreeExpandedBehavior GetOrCreateBehavior(Tree targetObject) { var behavior = targetObject.GetValue(TreeExpandedBehaviorProperty) as TreeExpandedBehavior; if (behavior == null) { behavior = new TreeExpandedBehavior(targetObject); targetObject.SetValue(TreeExpandedBehaviorProperty, behavior); } return behavior; } }
这样我们在ViewModel中就可以通过命令来处理TreeViewItem的Expanded、Collapsed事件了,并且可以获取到TreeViewItem的数据。
最后实现效果如图:
最后附上demo的代码,供大家参考。有不对的地方,希望大家多多建言拍砖。
出处:http://www.cnblogs.com/rpoplar/
本文版权归作者【rpoplar】和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究其法律责任的权利。