Virtual Treeview 的实现

来源:互联网 发布:java高级for循环 编辑:程序博客网 时间:2024/05/15 16:22
 
Virtual Treeview 的实现


作者: Andrew D. Weiss.
翻译: [yn]Yuxiang

本文介绍如何简单实现同步或异步载入节点的 Virtual treeview 。

C# (C# 1.0, C# 2.0)
Windows, .NET (.NET 1.0, .NET 1.1, .NET 2.0, .NET 3.0)
Win32, VS (VS.NET2002, VS.NET2003, VS2005)
Dev
上传时间: 2007-9-18

下载演示程序(http://www.codeproject.com/useritems/Virtual_Treeview/VirtualTreeview_Demo.zip) - 4.7 KB
下载源代码(http://www.codeproject.com/useritems/Virtual_Treeview/VirtualTreeview_Source.zip) - 10.4 KB
原文地址(http://www.codeproject.com/useritems/Virtual_Treeview.asp)


image

简介

这是我的第一篇博客,因此我选择了一个简单的话题来讨论:如何实现最简单的 virtual treeview

在我开发的每个项目中,遇到了处理大量数据和大量元数据的问题,提供搜索功能使得浏览大量信息变为可能,但是在某些地方,需要使用 Treeview、 Listview 或 Grid 来描述某些未知数量的信息。如何来操作这些信息,使你的应用程序可升级,或避免因冲突导致的当机。

通过用户界面控件 "虚拟化(Virtual)",来保持锁定的用户界面线程继续运行是重要的。不要载入那些决不会被查看(或不需要)的大量数据。 虚拟化 (在这里) 只不过意味着,只载入正在对用户显示的数据,但是却能给人以有很多的数据的视觉感受。.NET 的 Listview 控件自身能实现虚拟视图(virtual view) ,因此 listview 的项目可以在需要时才被载入;最流行的第三方 grid 控件也有某些虚拟化支持(virtual support),但是 .Net 的Treeview 控件不支持这样……

代码使用
同步的 Virtual Treeview

实现同步 virtual treeview 的逻辑非常简单,但它的缺点不可避免。
1. 载入根节点
2. 以名称 VIRT 为每个根节点添加子节点,这会导致 treeview 在每个节点前放置一个加号
3. 捕获 onBeforeExpand 事件,如果 VIRT 节点是正在被展开节点的子节点,则把它替换为“实体”子节点
4. 为每个新增的子节点添加一个 VIRT 节点 (除非你知道它确实是一个末级节点)

private void treeVirt1_BeforeExpand( object sender, TreeViewCancelEventArgs e ){   // 如果正在被展开的节点包含一个虚节点(virtual node),则在请求时,我们必须载入此节点的子节点。   // 如果它不包含虚节点,则不需要再做什么(因为它本身就是末结点?)   if( e.Node.Nodes.ContainsKey( VIRTUALNODE ) )   {      try      {         // 载入数据         // 注意:这将需要一些时间,可能会让你的用户不耐烦         // 参阅下面的异步实现版本         // 清除所有子节点         e.Node.Nodes.Clear();         // 载入新的子节点到 treeview 中         string[] arrChildren = new string[] { "Grapes", "Apples", "Tomatoes", "Kiwi" };         foreach( string sChild in arrChildren )         {            // 请确保添加虚节点到“可能”有子节点的新项目中。如果你确实知道此项目为末级节点,则不需要为它添加虚节点            TreeNode tNode = e.Node.Nodes.Add( sChild );            AddVirtualNode( tNode );         }      }      catch      {         // 发生错误,重置         e.Node.Nodes.Clear();         AddVirtualNode( e.Node );      }   }}


实现这些规则非常简单,如果有必要,延长数据载入时间。对于一个简单的应用,全部数据是本地的并且可被快速载入时,这种方法是好的。但是,如果你需要从服务器来调用和载入那些(会耗费大量时间的)节点数据……通常……在用户界面线程这样做,将会导致整个应用程序被锁定。如果使用后台工作线程,来扩展这些规则,实现同步载入数据,则实用价值不高。

异步的 Virtual Treeview

异步 virtual treeview 的实现逻辑略有不同。我们捕获 OnAfterExpand 事件,而不是 OnBeforeExpand 事件;然后使用一个 BackgroundWorker 线程,以便于能在用户界面线程以外的另一个线程中载入数据 (耗时操作) 。从其它线程中,我们不能触及用户界面对象,但这也是 BackgroundWorker 线程的好处,它的回调事件发生在用户界面线程。
因此,当 RunWorkerCompleted 事件发生时,你可以取得在其它线程中载入的数据,并在它之外创建 TreeNode 。当 RunWorkerCompleted 事件执行时,你需要知道哪些 TreeNode 开始了请求(这样我们就能知道应该把新节点添加到哪里),因此我们传递它们到 BackgroundWorker 线程中(so we'll send it "along for the ride" on the BackgroundWorker thread),但是请记住在 BackgroundWorker 线程的 DoWork 函数中,不要使用 TreeNode !你不能在创建控件的用户界面线程以外的其它线程中,使用这些控件。(事实并非如此绝对,你也可以使用 BeginInvoke,这可能会在我的下篇博客中讨论)
1. 载入根节点
2. 以名称 VIRT 为每个根节点添加子节点,这会导致 treeview 在每个节点前放置一个加号
3. 捕获 onAfterExpand 事件
4. 建立一个 BackgroundWorker 线程,并传递 TreeNode 及其它必要的信息到 DoWork 函数
5. 在后台线程的 DoWork 函数中执行耗时的操作
6. 从耗时操作返回原 TreeNode 和结果
7. 从 RunWorkerCompleted 事件中的服务,取得 TreeNode 和数据
8. 移除 VIRT 节点,并把它替换为新的 TreeNodes
9. 为每个新增的子节点添加一个 VIRT 节点 (除非你知道它确实是一个末级节点)

#region Asynchronous Treeviewprivate void treeVirt2_AfterExpand( object sender, TreeViewEventArgs e ){   if( e.Node.Nodes.ContainsKey( VIRTUALNODE ) )   {      BackgroundWorker bw = new BackgroundWorker();      bw.DoWork += new DoWorkEventHandler( bw_DoWork );      bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bw_RunWorkerCompleted );      object[] oArgs = new object[] { e.Node, "Some information..." };                  bw.RunWorkerAsync( oArgs );   }}private void bw_DoWork( object sender, DoWorkEventArgs e ){   object[] oArgs = e.Argument as object[];   TreeNode tNodeParent = oArgs[0] as TreeNode;   string sInfo = oArgs[1].ToString();   // 注意:在此处不能使用 tNodeParent,因为不是在用户界面线程中(参阅 Invoke)   // We've only passed it in so we can round trip it to the bw_RunWorkerCompleted event.      // 使用 sInfo 参数载入数据   Random r = new Random();   Thread.Sleep( r.Next( 500, 2500 ) );   string[] arrChildren = new string[] { "Grapes", "Apples", "Tomatoes", "Kiwi" };   // 返回父节点和子节点列表到用户界面线程中   e.Result = new object[] { tNodeParent, arrChildren };}private void bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ){   // 从后台工作线程中取得父节点和子节点列表   object[] oResult = e.Result as object[];   TreeNode tNodeParent = oResult[0] as TreeNode;   string[] arrChildren = oResult[1] as string[];   tNodeParent.Nodes.Clear();   foreach( string sChild in arrChildren )   {      TreeNode tNode = tNodeParent.Nodes.Add( sChild );      AddVirtualNode( tNode );   }}#endregion

助手功能

// 助手功能被上面的方法调用。  // 以特定的 "VIRT" 名称,简单地添加一个 "Loading..." 节点到正在载入数据的父节点(提示用户)private const string VIRTUALNODE = "VIRT";private void AddVirtualNode( TreeNode tNode ){   TreeNode tVirt = new TreeNode();   tVirt.Text = "Loading...";   tVirt.Name = VIRTUALNODE;   tVirt.ForeColor = Color.Blue;   tVirt.NodeFont = new Font( "Microsoft Sans Serif", 8.25F, FontStyle.Underline);   tNode.Nodes.Add( tVirt );}
原创粉丝点击