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的子节点的都能够处理这两个事件。这里有点拗...直接上代码大家就明白了:

TreeItem
 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);
            }
        }
         
    }
Tree
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行为的实现类似,不再重复。

TreeExpandedBehavior
 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的代码,供大家参考。有不对的地方,希望大家多多建言拍砖。

MVVMTree.zip

posted @ 2013-01-02 13:31  rpoplar  阅读(808)  评论(0编辑  收藏  举报