The ASPxTreeList is a hybrid tree and grid display control. It works in unbound and bound modes and includes a virtual mode. Virtual mode—the subject or our example—is great for large data sets. A challenge related to building a tree from large amounts of data is reading the data, building all of the tree nodes and child nodes, and then transmitting that data back to the client. The example included demonstrates how the ASPxTreeList supports unbound data and large amounts of data using virtual mode.
Virtual mode essentially works in this way: part of the tree is populated up front. When a subordinate part is requested then just that additional subordinate part is created in addition to existing parts. Translated to the ASPxTreeList this means one or more top-level nodes are created. You code two events that respond when a top level node is expanded and child nodes consequently need to be populated too. The virtual mode events populate the node-to-be-expanded at the time of the request rather than all up front.
A good stress-test example might be using the ASPxTreeList to represent the file system of a computer. With terabyte drives the sheer amount of data would definitely confound most system’s memory and exceed available bandwidth. In this instance an entire representation of a computer’s file system would be physically as well as logically impractical. Physical impracticability exists due to hardware limitations and logically impracticable is present because a person probably wants to look at a single node at a time—for example, a person probably wants to copy files in a single folder or pick a single file to modify.
To demonstrate, my new laptop has about 80 gigabytes of data. The data are represented as files and directories, and in this particular instance the files and directories number 574,801. Represented as a tree structure this is a tremendous number of nodes and child nodes, and doing so as a Web page is physically impractical and no human user needs to see or is patient enough to wait for all 574,801 elements. To qualify my comments, it is worth stating that some operations—like searching and sorting—may require an examination of all files, but viewing the file system in a hierarchical way does not require that all files be examined. More likely when a tree structure is needed then the general use will be drilling down into various nodes based on interest in the contents of a particular node. Subsequently only nodes of interest need be populated at the time of interest, resulting in a reduction in tree population times and bandwidth requirements.
Listing 1 provides an example that demonstrates how to use the virtual mode feature of the ASPxTreeList to create a view-only representation of a PCs file system. Listing 2 provides the ASPX for the sample. After the listing are the steps for re-creating the sample and an explanation of the code-behind (the code in Listing 1).
Listing 1: Using the Virtual Mode capability of the ASPxTreeList to display the contents of a file system.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using DevExpress.Web.ASPxTreeList;
using System.Security.Permissions;
public partial class _Default : System.Web.UI.Page
{
private static readonly string FOLDER_ICON = "~/images/folder.gif";
private static readonly string FILE_ICON = "~/images/file.gif";
private static IEnumerable<FileInfo> GetFileInfoData(IEnumerable<string> files)
{
return from file in files
let info = new FileInfo(file)
where (info.Attributes
& FileAttributes.Hidden) != FileAttributes.Hidden
select info;
}
private static IEnumerable<string> GetFiles(string start)
{
return
Directory.GetDirectories(start).Union(Directory.GetFiles(start));
}
private string GetIcon(FileInfo info)
{
return (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory
? FOLDER_ICON : FILE_ICON;
}
protected void ASPxTreeList1_VirtualModeCreateChildren(object sender, TreeListVirtualModeCreateChildrenEventArgs e)
{
e.Children = new List<FileInfo>();
if (e.NodeObject == null)
{
FileInfo info = new FileInfo("C:\\");
e.Children.Add(info);
}
else
{
string path = e.NodeObject == null ? "C:\\"
: e.NodeObject.ToString();
if (!Directory.Exists(path)) return;
try
{
var files = GetFiles(path);
var infos = GetFileInfoData(files);
e.Children = new List<FileInfo>();
foreach (var info in infos)
{
e.Children.Add(info);
}
}
catch { }
}
}
protected void ASPxTreeList1_VirtualModeNodeCreating(object sender, TreeListVirtualModeNodeCreatingEventArgs e)
{
FileInfo info = (FileInfo)e.NodeObject;
e.NodeKeyValue = GetNodeGuid(info.FullName);
e.IsLeaf = !Directory.Exists(info.FullName);
if(info.FullName == "C:\\")
e.SetNodeValue("Name", "C:\\");
else
e.SetNodeValue("Name", info.Name);
e.SetNodeValue("IconName", GetIcon(info));
e.SetNodeValue("NodePath", info.FullName);
}
// need a unique key, so GUID is used
private Guid GetNodeGuid(string path)
{
if (!Map.ContainsKey(path))
Map[path] = Guid.NewGuid();
return Map[path];
}
private Dictionary<string, Guid> Map
{
get
{
const string key = "DX_PATH_GUID_MAP";
if (Session[key] == null)
Session[key] = new Dictionary<string, Guid>();
return Session[key] as Dictionary<string, Guid>;
}
}
}
Listing 2: The code-behind for the ASPxTreeList sample.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register assembly="DevExpress.Web.ASPxTreeList.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.Web.ASPxTreeList" tagprefix="dxwtl" %>
<%@ Register assembly="DevExpress.Web.ASPxEditors.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.Web.ASPxEditors" tagprefix="dxe" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<dxwtl:ASPxTreeList ID="ASPxTreeList1" runat="server"
onvirtualmodecreatechildren="ASPxTreeList1_VirtualModeCreateChildren"
onvirtualmodenodecreating="ASPxTreeList1_VirtualModeNodeCreating"
AutoGenerateColumns="False" EnableCallbackCompression="True">
<Settings ShowColumnHeaders="False" />
<SettingsBehavior ExpandCollapseAction="NodeDblClick" />
<Templates>
<DataCell>
<dxe:ASPxImage ID="ASPxImage1" runat="server" ImageUrl='<%# Container.GetValue("IconName") %>'>
</dxe:ASPxImage>
<dxe:ASPxLabel ID="ASPxLabel1" runat="server" Text='<%# Container.GetValue("Name") %>'>
</dxe:ASPxLabel>
</DataCell>
</Templates>
<Columns>
<dxwtl:TreeListTextColumn FieldName="Name" VisibleIndex="0">
</dxwtl:TreeListTextColumn>
</Columns>
</dxwtl:ASPxTreeList>
</div>
</form>
</body>
</html>
To re-create the example follow these steps:
- Create a Web site application
- Drop an ASPxTreeList in the <div> section of the page
- From the Smart tags menu for the ASPxTreeList select Edit Templates and pick the DataCell template
- Drop an ASpxImage followed by an ASPxLabel into the DataCell template
- From the Smart tags menu select End Template Editing
- As shown in Listing 2 bind the ASPxImage ImageUrl value to IconName and the ASPxLabel’s Text property to Name. (You provide these values in the code)
- Again, from the Smart tags menu click the Columns menu item and add a column. Set the FieldName property to”Name”. This will be used to represent the Name value described in step 6
- Close the Columns Editor Form
- Using Windows explorer find some icons to represent a folder and file. Create a project folder named images and copy these icons/graphics into the images folder
- Use the designer to stub out the VirtualModeCreateChildren and VirtualModeNodeCreating event handlers for the ASPxTreeList
Figure 1: Add a column with a FieldName value of “Name”, which will be used to contain the folder and file information.
That’s it. Now you need to implement the code in Listing 1. VirtualModeCreateChildren creates the child nodes and VirtualModeNodeCreating provides detail information for the child nodes. The way this example works specifically is that the first time that VirtualModeCreateChildren is called e.NodeObject will be null. This is used as a cue to create the root node. The nodes are represented as file system objects (FileInfo), and nodes are added to the event argument’s Children property. (Notice that the code itself creates a container for the Children property; in this case List<FileInfo>.) When a node is created VirtualModeNodeCreating is called. This event handler provides detail for the node. In the example in Listing 1 the IsLeaf, Name, IconName, and NodePath values are provided. IsLeaf is used to determine if there are children for this element; in the code it is assumed only non-directories are leafs. SetNodeValue(“Name”, string) is used to set the display name for the node, IconName is used to map an image to the ASPxImage control, and NodePath stores the full path name for possible use.
When the demo runs only the root node will be created. When a user clicks on the root node—C:\—the VirtualModeCreateChildren and VirtualModeNodeCreating duo spring into action again. This time the else part of VirtualModeCreateChildren is run and C:\’s files and folders are read. GetFiles returns all of the files and directories and GetFileInfoData uses LINQ to query all of those elements, excluding hidden files, and create an enumerable collection of FileInfo objects. Each of the resultant objects are added as children to the TreeListVirtualModeCreateChildrenEventArgs event parameter’s Children property and consequently the VirtualModeNodeCreating event is called for each of these items, and their details are filled in to populate the ASPxImage and ASPxLabel. Essentially the two events are acting in tandem to produce a result that is like a recursive descent algorithm.
The only other code of note is GetNodeGuid and the Map property. The Map property represents a dictionary mapping file paths to Guids. GetNodeGuid figures out if a node has already been created and associated with a Guid or if a pairing needs to be added to the Dictionary. This code while trivial in appearance is essential. The ASPxTreeList needs a means of distinctly differentiating between nodes and GUIDs are distinct. Unfortunately without this code expanding nodes doesn’t work. (You don’t need this code with databound uses of the ASPxTreeList, but I’d like to see it go away for unbound data too. My first instinct is that the nature of a tree-path is distinct, making the necessity of a unique key redundant. I am in negotiations right now with the grid-code owner to mitigate the unique key requirement. I suspect it is the grid-like use of the ASPxTreeList that makes keys helpful.) The results of the demo running on my laptop are shown in Figure 2.
Figure 2: The basic explorer Web page running on my laptop.