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的数据。
最后实现效果如图:

Silverlight TreeView MVVM 绑定实现

最后附上demo的代码,供大家参考。有不对的地方,希望大家多多建言拍砖。

MVVMTree.zip

你可能感兴趣的:(silverlight)