WPF对绑定到数据的树初始化选择项

【需求】

1——

对一棵绑定到数据源的树,当初始化加载后,设置树的初始化选择对象。

比如:当我们实现对行业的选择树时,希望当打开这棵树的时候,树能自动把之前已经选择的或者系统默认的树标记为选择状态。

2——

数据源是一个动态加载的集合,即:当我们展开或选择某一个数据节点的时候,系统自动实现该数据节点下级子数据集合的加载

如Windows的文件系统,资源管理器不是一次性的把整个文件系统都加载在内存,而是当我们需要打开某一个文件夹的时候,文件系统才会去加载这个文件夹的下级文件信息。

 

【问题】

1——

当TreeViewItem被选择或展开后自动加载下级数据集合需要控制需要额外编写代码实现

2——

当TreeView绑定到数据的时候,封闭了开发人员直接使用TreeViewItem的可能,要操纵TreeViewItem需要通过特殊的手段来实现。

3——

WPF中的TreeView不可以通过设置SelectedItem(只读)来实现当前选择对象的设定。

 

【解决】

1——

可以通过路由事件跟踪,TreeViewItem的SelectedEvent和ExpandedEvent是可路由事件,因此通过在TreeView或其上级容器中跟踪这两个路由事件来达到目的:

 

     protected  override  void OnInitialized(EventArgs e)
    {
        this.AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(TreeViewItemHandler));
        this.AddHandler(TreeViewItem.SelectedEvent, new RoutedEventHandler(TreeViewItemHandler));
         base.OnInitialized(e);
    }

     void TreeViewItemHandler( object sender, RoutedEventArgs e)
    {
        TreeViewItemEx item = e.OriginalSource  as TreeViewItemEx;
         if (item !=  null && item.DataContext  is FileFolder)
        {
            (item.DataContext  as FileFolder).Load();
        }
    }

 

 2——

如问题1的解决方案,当跟踪到TreeViewItem被选择或展开时,获取该TreeViewItem的DataContext(即数据对象)并执行数据的加载:

 

        TreeViewItemEx item = e.OriginalSource  as TreeViewItemEx;
         if (item !=  null && item.DataContext  is FileFolder)
        {
            (item.DataContext  as FileFolder).Load();
        }

 

3——

因为SelectedItem是只读的,而且,TreeView也没有提供设置初始化选择项的功能,要达到目的就只能自己想办法了。

由于TreeView的树结构中没有提供直接访问树节点对象的支持,我们只能通过其对象集合Items属性来获取数据对象,在查阅相关资料后,找到了通过数据对象来获取对应的TreeViewItem的方法:

 

     public  static TreeViewItemEx GetChildItem(ItemsControl container,  object data)
    {
         if (container ==  null || data ==  null)
        {
             return  null;
        }
         try
        {
            TreeViewItemEx result = container.ItemContainerGenerator.ContainerFromItem(data)  as TreeViewItemEx;
             if (result !=  null)
            {
                 return result;
            }
        }
         catch
        {
        }
         foreach ( var item  in container.Items)
        {
             if (item ==  null)
            {
                 continue;
            }
             if (item  is TreeViewItemEx && (item  as TreeViewItemEx).DataContext !=  null && (item  as TreeViewItemEx).DataContext.Equals(data))
            {
                 return item  as TreeViewItemEx;
            }
             if (item.Equals(data))
            {
                 return container.ItemContainerGenerator.ContainerFromItem(item)  as TreeViewItemEx;
            }
        }
         return  null;
    }

 

【方案】

1——

要达到指定数据对象对应的TreeViewItem的选择,必须有一条从树的根节点到到目标节点的数据路径,这里定义这个数据路径为:LinkList<object> list,获取这个数据路径的方法为:

 

     public LinkedList< object> GetDataPathFromRoute( object dataItem)
    {
        LinkedList< object> list =  new LinkedList< object>();
         this.GetDataPathFromRoute(list, dataItem);
         return list;
    }

     private  void GetDataPathFromRoute(LinkedList< object> list,  object dataItem)
    {
         if (dataItem ==  null)
        {
             return;
        }
         object parent = GetParentObject(dataItem);
         if (IsStopObject(dataItem))
        {
             if (dataItem ==  null)
            {
                 return;
            }
            list.AddLast(dataItem);
             return;
        }
         this.GetDataPathFromRoute(list, parent);
        list.AddLast(dataItem);
    }

     private  bool IsStopObject( object dataItem)
    {
         if (dataItem ==  null)
        {
             return  true;
        }
         if (dataItem  is  string &&  string.IsNullOrEmpty(dataItem  as  string))
        {
             return  true;
        }
         return  this.Equals(dataItem);
    }

     private  object GetParentObject( object dataItem)
    {
         if (dataItem  is FileItemInfoBase)
        {
             return  this.GetParentObject((dataItem  as FileItemInfoBase).FileItem);
        }
         if (dataItem  is FileInfo)
        {
             return (dataItem  as FileInfo).Directory;
        }
         if (dataItem  is DirectoryInfo)
        {
             if ((dataItem  as DirectoryInfo).Root == dataItem)
            {
                 return  null;
            }
             return (dataItem  as DirectoryInfo).Parent;
        }
         if (dataItem  is  string)
        {
             if (File.Exists(dataItem  as  string))
            {
                 return  new FileInfo(dataItem  as  string).Directory;
            }
             return  new DirectoryInfo(dataItem  as  string).Parent;
        }
         return  null;
    }

 

2——

数据路径树节点的展开:

     public  object InitializeItem
    {
         set
        {
            LinkedList< object> list = GetDataPathFromRoute(value);
             if (list !=  null && list.First !=  null)
            {
                LinkedListNode< object> node = list.First;
                ItemsControl container = treeView;
                 while (node !=  null)
                {
                    TreeViewItemEx item = GetChildItem(container, node);
                     if (item ==  null)
                    {
                        container =  null;
                         return;
                    }
                    item.IsExpanded =  true;
                    container = item;
                    node = node.Next;
                }
                 if (container !=  null && container  is TreeViewItem)
                {
                    (container  as TreeViewItem).IsSelected =  true;
                }
            }
        }
    }

在实际的运行中,这段代码并不可行,除非数据树(TreeView)的数据源是初始化全加载的(个人臆测,这个没验证过),因为当我们执行TreeViewItem的展开操作的时候,是正确的执行的下级数据集合的加载,但此时,没有办法通过前面的GetChildItem来获取目标的TreeViewItem对象,返回的始终是空。

经过分析,认为TreeViewItem的子对象的创建有一个延迟(跟初始化数据操作InitializeItem在同一个线程中),从而导致了即便数据加载了,而TreeViewItem的下级UI对象还没有创建而是空的。

 

3——

经过对TreeViewItem的各个事件(以及可重载函数=属性)进行跟踪,发现,当加载TreeViewItem的下级数据的时候,会触发ItemsSourceChanged事件,紧接着,会触发OnRender方法的执行,而且,OnRender方法执行后,下级TreeViewItem对象也就已经创建了,因此代码修改如下:

 

         private  void setCurrPath_Click( object sender, RoutedEventArgs e)
        {
             if (Directory.Exists( this.rootPath.Text) &&  this.Folder !=  null)
            {
                DirectoryInfo dir =  new DirectoryInfo( this.currPath.Text);
                LinkedList< object> list =  this.Folder.GetDataPathFromRoute(dir);
                 if (list.First !=  null)
                {
                     this.treeViewEx1.SetInitializeSelectedItem(list.First);
                }
            }
        }

TreeViewEx类的SetInitializeItem方法代码如下:

         public  void SetInitializeSelectedItem(LinkedListNode< object> dataLink)
        {
             if (dataLink ==  null || dataLink.Value ==  null)
            {
                 return;
            }
            TreeViewItemEx item = TreeViewItemEx.GetChildItem( this, dataLink.Value);
             if (item ==  null &&  this.Items.Count ==  1)
            {
                item = TreeViewItemEx.GetChildItem(TreeViewItemEx.GetChildItem( thisthis.Items[ 0]), dataLink.Value);
            }
             if (item !=  null)
            {
                 if (dataLink.Next !=  null && dataLink.Next.Value !=  null)
                {
                    item.SetInitializeSelectedItem(dataLink.Next);
                }
                 else
                {
                    item.IsSelected =  true;
                }
            }
        }

TreeViewItemEx对应的代码如下:

         private  bool _rendered =  false;
         protected  override  void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
        {
             this._rendered =  false;
             base.OnItemsSourceChanged(oldValue, newValue);
        }

         protected  override  void OnRender(DrawingContext drawingContext)
        {
             if (!_rendered)
            {
                 this.SetInitializeSelectedItem();
                 this._rendered =  true;
            }
             base.OnRender(drawingContext);
        }

         protected  override  void OnSelected(RoutedEventArgs e)
        {
             this.IsExpanded =  true;
             base.OnSelected(e);
        }

         private  bool _mustInitializeSelectedItem =  false;
         private LinkedListNode< object> _initializeSelectedItem =  null;
         internal  void SetInitializeSelectedItem(LinkedListNode< object> dataLink)
        {
             if (dataLink ==  null || dataLink.Value ==  null)
            {
                 return;
            }
             this._mustInitializeSelectedItem =  true;
             this._initializeSelectedItem = dataLink;
             if ( this._rendered)
            {
                 this.InitializeSelectedItem( false);
            }
        }

         private  void InitializeSelectedItem( bool rendered)
        {
            TreeViewItemEx childItem = GetChildItem( thisthis._initializeSelectedItem.Value);
             if (childItem !=  null)
            {
                 if (!childItem.IsExpanded &&  this._initializeSelectedItem.Next !=  null &&  this._initializeSelectedItem.Next.Value !=  null)
                {
                    childItem.IsExpanded =  true;
                    childItem.SetInitializeSelectedItem( this._initializeSelectedItem.Next);
                }
                 else
                {
                    childItem.IsSelected =  true;
                }
            }
             else
            {
                 if ( this.IsExpanded || rendered)
                {
                     this.IsSelected =  true;
                }
                 else
                {
                     this.IsExpanded =  true;
                }
            }
        }

         private  void SetInitializeSelectedItem()
        {
             if (! this._mustInitializeSelectedItem)
            {
                 return;
            }
            _mustInitializeSelectedItem =  false;
             this.InitializeSelectedItem( true);
        }

 

至此,需求已经达到。

 

【存在问题】

在以上的解决方案中,理论上,问题已经圆满的解决(甚至自以为是完美的解决了),但在实际使用中,却出现新的问题:

不稳定,有时能成功展开,达到目的(跟树的路径深度无关,不过,树的路径深度低的话,成功的可能性大)。

附参考代码程序。
/Files/Daview/WpfApplication1.zip


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值