{"id":493,"date":"2009-01-05T00:00:00","date_gmt":"2009-01-05T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/rendering-hierarchical-data-with-the-treeview\/"},"modified":"2021-05-17T18:35:04","modified_gmt":"2021-05-17T18:35:04","slug":"rendering-hierarchical-data-with-the-treeview","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/rendering-hierarchical-data-with-the-treeview\/","title":{"rendered":"Rendering Hierarchical Data with the Treeview"},"content":{"rendered":"<div id=\"pretty\">\n<h2>Using Nested Sets with \u00a0the Treeview<\/h2>\n<p>If you&#8217;re like me, one day Joe Celko exposed you to <a href=\"http:\/\/kamfonas.com\/wordpress\/wp-content\/uploads\/2007\/08\/recursive-hierarchies-no-columns.htm\">Michael J. Kamfonas&#8217; Nested Sets model <\/a>for trees in SQL on the Usenet, and it changed your life. So you have an easy way of expressing hierarchical data in SQL, with ordered sibling nodes, no less.<\/p>\n<p>A decade or so later, we find ourselves trying to integrate our database design patterns with the .NET controls. Data-binding handles a lot of the functional requirements for you, but you have to do it pretty much the way that Microsoft expects you to, which you&#8217;ll understand if you try to set the DataSource on a <b>FormView <\/b>control. We have to give up middle layer logic and tie our user interface directly to the database objects, which is not generally a good idea, but not always worth avoiding.<\/p>\n<p>The .NET control for hierarchical data is the <b>TreeView,<\/b> and the <b>TreeView<\/b> requires an <b><b>IHierarchicalDataSource<\/b> <\/b>object. However, if you try to do this through the GUI, you&#8217;ll see there is no way that is provided to add a connection to a SQL Server database.<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-first.png\" alt=\"623-first.png\" width=\"294\" height=\"326\" \/><\/p>\n<p class=\"caption\">Figure 1 TreeView control in Toolbox under &#8216;Navigation&#8217;<\/p>\n<p class=\"illustration\">\u00a0<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Second.png\" alt=\"623-Second.png\" width=\"623\" height=\"514\" \/><\/p>\n<p class=\"caption\">Figure 2: Options for a TreeView data source<\/p>\n<p>It seems nested sets aren&#8217;t so pervasive that .NET provides a UI component set. The point of this article, though, is to fill that gap. So, let&#8217;s back up a little.<\/p>\n<h2>Populating a Treeview with Nested sets<\/h2>\n<h3>The Database bit.<\/h3>\n<p>In the nested set model, every node has two integer values which (after Celko) I call <b>lft<\/b> and <b>rgt<\/b> . <b>rgt<\/b> is always greater than <b>lft<\/b> , but only one greater for leaf nodes. If one node&#8217;s <b>lft<\/b> is less than another node&#8217;s <b>lft<\/b> , and the first node&#8217;s <b>rgt<\/b> is greater than the next node&#8217;s <b>rgt<\/b> , the first node is an ancestor of the second node. There is a lot more to nested sets, and many wonderful reasons to use them. However, this article is about showing them in a .NET TreeView, so I&#8217;ll refer you to Celko&#8217;s &#8220;<a href=\"http:\/\/www.bookpool.com\/sm\/0123693799\">SQL for Smarties<\/a>&#8221; or &#8220;<a href=\"http:\/\/www.bookpool.com\/sm\/1558609202\">Trees and Hierarchies in SQL For Smarties<\/a>&#8221; for a discussion. Now, we&#8217;ll just make one, with data enough to populate a TreeView.<\/p>\n<pre class=\"lang:c# theme:vs2012\">USE SQLTreeNode\r\nGO\r\n-- drop the schemabound function\r\nIF OBJEcT_ID('GetTreeNodeParentPath', 'FN') IS NOT NULL \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DROP FUNCTION GetTreeNodeParentPath\r\nGO\r\n-- create a table to store node data. \r\nIF OBJECT_ID('TreeNode', 'U') IS NOT NULL DROP TABLE TreeNode\r\nIF OBJECT_ID('Node', 'U') IS NOT NULL DROP TABLE Node\r\nGO\r\nCREATE TABLE Node(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NodeID INT IDENTITY(0,1) CONSTRAINT PK_NODE PRIMARY KEY Clustered,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 TEXT NVARCHAR(255) NOT NULL CONSTRAINT NOSLASH 0 = CHARINDEX('\/', Text),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 TOOLTIP NVARCHAR(MAX),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IMAGEURL NVARCHAR(4000),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Checked BIT DEFAULT 0\r\n)\r\nGO\r\n\/** create a table to place nodes in the hierarchy.\r\n\u00a0 * note that the node data are stored separately from the node position to \r\n\u00a0 * allow a node to appear in the tree multiple times\r\n\u00a0 *\/\r\nGO\r\nCREATE TABLE TreeNode(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 lft INT UNIQUE, -- we would cluster on lft if we were using a single table\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rgt INT UNIQUE,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT PK_TreeNode PRIMARY KEY NONCLUSTERED(lft , rgt ),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NODEID INT CONSTRAINT TreeNodeNode FOREIGN KEY (NODEID) REFERENCES NODE(NODEID)\r\n)\r\nGO\r\n-- We're going to want that merge join\r\nCREATE CLUSTERED INDEX IX_TreeNode ON TreeNode(NodeID)\r\nGO\r\n\/** This procedure lets us check for bad nodes. It's for development and schema \r\n\u00a0 * Maintenance\r\n\u00a0 *\/\r\nIF OBJEcT_ID('CheckTreeNodeTable', 'P') IS NOT NULL \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DROP PROCEDURE CheckTreeNodeTable\r\nGO\r\nCREATE PROCEDURE CheckTreeNodeTable AS\r\nBEGIN\r\n\u00a0\u00a0\u00a0 IF EXISTS(SELECT * FROM TreeNode tvd1 INNER JOIN TreeNode tvd2 ON \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0tvd1.lft = tvd2.rgt ) RAISERROR('lft and rgt together are not unique', 16, 1)\r\n\u00a0\u00a0\u00a0 IF 1 &lt;&gt; (SELECT 2 * COUNT(*) - MAX(rgt ) + MIN(lft ) FROM TreeNode) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RAISERROR('Gaps exist in lft and rgt values', 16, 1)\r\n\u00a0\u00a0\u00a0 IF EXISTS(SELECT * FROM TreeNode, TreeNode tvd2 WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (TreeNode.lft &lt; tvd2.lft AND TreeNode.lft &gt; tvd2.rgt ) AND \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (TreeNode.rgt &gt; tvd2.lft AND TreeNode.rgt &lt; tvd2.rgt )) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RAISERROR('Incorrectly nested nodes are defined', 16, 1)\r\nEND\r\nGO\r\n\/** Add a root node\r\n\u00a0 *\/\r\nINSERT INTO Node([TEXT], TOOLTIP, IMAGEURL, Checked) VALUES (N'Root', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 N'This is the node from which all other nodes descend', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 N'http:\/\/example.com\/myicon.png', 0 )\r\nINSERT INTO TreeNode (lft , rgt , NodeID) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 VALUES (0, 1, @@IDENTITY)\r\nGO\r\nSET ANSI_NULLS ON\r\nGO\r\n\/** Return's the path to the node's parent \r\n\u00a0 * @lft the value for the node's left edge\r\n*\/\r\nCREATE FUNCTION GetTreeNodeParentPath(@lft INT) RETURNS nvarchar(max) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 with schemabinding AS BEGIN\r\n\u00a0\u00a0\u00a0 DEcLARE @RETVAL nvarchar(max), @rgt INT\r\n\u00a0\u00a0\u00a0 SelEcT @rgt = rgt FROM dbo.TreeNode WHERE lft = @lft \r\n\u00a0\u00a0\u00a0 -- return the node's parent's slash-delimited path \r\n\u00a0\u00a0\u00a0 SELEcT @RETVAL = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- Initialize @Retval with an empty string\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 COALESCE(@RETVAL + '\/', SPACE(0)) + Text FROM dbo.TreeNode \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER JOIN dbo.Node ON dbo.TreeNode.NodeID = dbo.Node.NodeID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- get all ancestor nodes\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE lft &lt; @lft AND rgt &gt; @rgt \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- get lower level nodes earlier\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ORDEr BY lft ASC\r\n\u00a0\u00a0\u00a0 RETURN @RETVAL\r\nEND\r\nGO\r\nIF OBJECT_ID('GetDescendingNodes', 'P') IS NOT NULL \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DROP PROCEDURE GetDescendingNodes\r\nGO\r\n\/** return all the nodes descending from the node with the specified path\r\n\u00a0 *\u00a0 We leave lft and rgt in the database, and deal with path enumeration out \r\n\u00a0 *\u00a0 in the world. We undo that here.\r\n\u00a0 * @Path slash-delimited path enumeration of ancestors of the node and the node\r\n\u00a0 * itself. Only this node and its descendants will be returned. If @Path is \r\n\u00a0 * null, return the entire tree.\r\n*\/\r\nCREATE PROCEDURE GetDescendingNodes(@PATH NVARCHAR(max) = NULL) AS BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DECLARE @rgt INT, @lft INT\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* For very large hierarchies, calculating the path for each node will \r\n\u00a0\u00a0\u00a0\u00a0 * become time-consuming and you may wish to rewrite the query finding a \r\n\u00a0\u00a0\u00a0\u00a0 * node by its path in the following way\r\n\u00a0\u00a0\u00a0\u00a0 * 1. Take the token before the first 'slash' and leave a remainder\r\n\u00a0\u00a0\u00a0\u00a0 * 2. Find the nth-level node with that text descending from the passed \r\n\u00a0\u00a0\u00a0\u00a0 *\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 node [a node's level is \r\n\u00a0\u00a0\u00a0\u00a0 *\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (count(*) where lft &lt; node.lft and rgt &gt; node.rgt )]\r\n\u00a0\u00a0\u00a0\u00a0 * 3. If the remainder has text, recurse with the found node and the \r\n\u00a0\u00a0\u00a0\u00a0 *\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 remainder\r\n\u00a0\u00a0\u00a0\u00a0 * 4. If the remainder is empty, return the node \r\n\u00a0\u00a0\u00a0\u00a0 * 5. Return all nodes with lft and rgt between that nodes lft and rgt as \r\n\u00a0\u00a0\u00a0\u00a0 *\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 below\r\n\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* Common Table Expressions get a bad rap as only enabling people who \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0* insist on using adjacency lists with CONNECT BY. But, we can cut our\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0* calls to GetTreeNodeParentPath in half using them here.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0*\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ;WITH TreeNodes(lft , rgt , ParentPath, TEXT, ToolTip, ImageURL, Checked) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AS (SELECT lft , rgt , dbo.GetTreeNodeParentPath(lft ), TEXT, ToolTip, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ImageURL, Checked FROM TreeNode INNER JOIN Node ON \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 TreeNode.NodeID = Node.NodeID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- Return the node and its descendants\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT tns.ParentPath, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 COALESCE(tns.ParentPath + '\/', SPACE(0)) + tns.TEXT AS PATH, tns.TEXT, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 tns.Tooltip, tns.ImageURL, tns.Checked \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM TreeNodes tns INNER JOIN TreeNodes anc \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- get the specified node and all descendants\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON tns.lft &gt;= anc.lft AND tns.rgt &lt;= anc.rgt \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- compare the nodes path with the argument\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND COALESCE(Anc.ParentPath + '\/', SPACE(0)) + Anc.[TEXT] = COALESCE(@PATH, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- get the root node if the argument is not specified\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (SELECT COALESCE(ParentPath + '\/', SPACE(0)) + Text \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM TreeNodes WHERE lft IN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- the root node has the lowest lft value\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (SELECT MIN(lft ) FROM TreeNodes)))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -- ordering by lft ensures nodes will precede their children\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ORDER BY tns.lft ASC\r\nEND\r\nGO\r\n\/** Add some nodes, just so there's something to see in the display\r\n\u00a0 * using Free Icons from \r\n\u00a0 * http:\/\/www.iconarchive.com\/category\/business\/people-icons-by-aha-soft.html\r\n\u00a0 * Clearly, you'll need CreateNode, ReviewNode, UpdateNode and DeleteNode\r\n\u00a0 * procedures, but those can wait until an article on the FormView ;)\r\n\u00a0 *\/\r\nDECLARE @ROOTID INT\r\nSELECT @ROOTID = NodeID FROM TreeNode WHERE lft = 0\u00a0 \r\nUPDATE TreeNode SET rgt = 19 WHERE nodeid = @ROOTID\r\nUPDATE Node SET TEXT = 'People', ImageURL = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/people-icon.jpg'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 , ToolTip = 'We''re all people, after all'\r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Boss', 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/boss-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Your boss is a person')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (1, 8, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES('Engineer', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/engineer-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Your boss could be an engineer')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (2, 5, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES('Footballer', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/footballer-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Engineers play a lot of soccer; I think they like the hexagons')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (3, 4, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES('Army Officer', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/army-officer-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Your boss could be an Army officer')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (6, 7, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES('Users', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/users-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Users are people')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (9, 18, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES('User Group', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/user-group-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Users can form groups')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (10, 11, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES('User', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/user-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Users can be individuated')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (12, 17, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip) VALUES('User Info', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/user-info-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Users have information')\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (13, 14, @@IDENTITY) \r\nINSERT INTO node (TEXT, ImageURL, Tooltip, Checked) VALUES('Check User', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'http:\/\/icons.iconarchive.com\/icons\/aha-soft\/people\/check-user-icon.jpg', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Users can be checked!', 1)\r\nINSERT INTO TreeNode (lft , rgt , NODEID) VALUES (15, 16, @@IDENTITY)\r\n\u00a0\r\n<\/pre>\n<p>That code contains a few different sections<\/p>\n<ol>\n<li>Making the data tables. We make one table to contain the data and another to locate it in the tree. This can be one table for simple implementations. Note that for a production implementation, you might want security, auditing and historical state functions, which are not included here<\/li>\n<li>This section starts with the GetParentPath function. It provides the &#8216;select&#8217; functionality for the TreeView control, for which we&#8217;ll shortly get to providing a client.<\/li>\n<li>The third section adds some data, leaning on some free icons I found on the web. Any practical implementation is going to provide Create\/Review\/Update\/Delete features, but since the TreeView doesn&#8217;t provide a way to use them, they&#8217;re out of scope here.<\/li>\n<\/ol>\n<h3>The Middle Bit<\/h3>\n<p>So, now, all that&#8217;s left to do is write some helper functions for the <b>IHierarchicalDataSource<\/b> , the <b>IHierarchicalDataSource<\/b> itself, and tie it to a TreeView. Really, we&#8217;ve defined the table, so the heavy lifting is done. I don&#8217;t know what those application developers go on about.<\/p>\n<p>What&#8217;s in an <b>IHierarchicalDataSource<\/b> ? The <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.web.ui\">documentation<\/a> says &#8221; The interface defines a single method, GetHierarchicalView, which retrieves a strongly typed <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.ui.hierarchicaldatasourceview(VS.80).aspx\">HierarchicalDataSourceView<\/a> object.&#8221; Our HierarchicalDataSourceView implementation, in turn, must override <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.ui.hierarchicaldatasourceview.select(VS.80).aspx\">HierarchicalDataSourceView.Select<\/a> and return a collection implementing <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.ui.ihierarchicalenumerable(VS.80).aspx\">IHierarchicalEnumerable<\/a>. So, let&#8217;s define our IHierarchicalEnumerable.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System.Web.UI;\r\nusing System.Collections;\r\n\u00a0\r\nnamespace SQLTreeView\r\n{\r\n\u00a0\u00a0\u00a0 \/** A collection of IHierarchyData objects -- nothing specific to \r\n\u00a0\u00a0\u00a0\u00a0 * SQLTreeNodeHierarchyData objects happens in this class.\r\n\u00a0\u00a0\u00a0\u00a0 *\/ \r\n\u00a0\u00a0\u00a0 public class HierarchicalEnumerable : ArrayList, IHierarchicalEnumerable\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Casts as IHierarchyData\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * returns null if that was not possible\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * @enumeratedItem object to cast as IHierarchyData\r\n\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0*\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public IHierarchyData GetHierarchyData(object enumeratedItem)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ not clear why this is an instance method\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return enumeratedItem as IHierarchyData;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 }\r\n}\r\nWe now need a class to implement the IHierarchyData object.\r\nusing System.Web.UI;\r\nusing System.Data.SqlTypes;\r\nusing System.Text.RegularExpressions;\r\n\u00a0\r\nnamespace SQLTreeView\r\n{\r\n\u00a0\u00a0\u00a0 \/** Class to transfer nested set data to the TreeView\r\n\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0 public class SQLTreeNodeData : IHierarchyData\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected static string _select = \"GetDescendingNodes\";\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected bool _hasChildren = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected bool _checked = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected string _toolTip = \"\";\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected string _text = \"\";\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected string _path = \"\";\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected string _imageURL = \"\";\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected HierarchicalEnumerable _children\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = new HierarchicalEnumerable();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected SQLTreeNodeData _parent = null;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected Regex _lasttoken = new Regex(\"([^\/]*)$\");\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\/** create a tree node with various display settings set\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * @path the text for each ancestor node in order from root to this,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\u00a0 delmited by slashed\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * @text Caption for the node\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * @tooltip pop-up mouseover text\r\n\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0* @imageURL locator for icon to display\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * @checkd whether the node is checked. 'checked' is a C# keyword.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public SQLTreeNodeData(string path, string text, string tooltip, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 string imageURL, bool checkd)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (_lasttoken.Matches(path)[0].ToString() != text) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 throw new System.Exception(text + \" is not the terminating token in \" + path);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.Path = path;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.Text = text;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.ToolTip = tooltip;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.ImageURL = imageURL;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ checked is misspelled because it is a keyword\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.Checked = checkd;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Return the path for ToString requests\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public override string ToString()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return this.Path;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** The stored procedure to call to collect the TreeNodes\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public static string Select\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 get\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return _select;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 set\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 _select = value;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ report if a children collection with non-zero count exists\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public bool HasChildren\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 get\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (null == _children || 0 == _children.Count)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return true;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** the slash-delimited text of the node and its ancestors\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public string Path{ get { return _path; } set { _path = value; }}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Text for the treeview node\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public string Text\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 get { return _text; }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 set { _text = value; }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** ToolTip to contextualize the node for the user\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public string ToolTip{get { return _toolTip; }set { _toolTip = value; }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Uniform resource identifier for a picture to display on the TreeView\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public string ImageURL {get {return _imageURL; }set {_imageURL = value;}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Whether to display the treenode as checked\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public bool Checked {get {return _checked; } set {_checked = value; } }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Accessor and Modifier for the collection of children\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public HierarchicalEnumerable Children\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 get { return _children; }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 set { _children = value; }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** specify which node contains this node\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public SQLTreeNodeData Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 get { return _parent; }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 set { _parent = value; }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** interface method to access Children property\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public IHierarchicalEnumerable GetChildren()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return this.Children;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** interface method to access Parent property\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public IHierarchyData GetParent()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return this.Parent;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** interface property to return this very object\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public object Item { get { return ((IHierarchyData)this); } }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** interface propery to wrap this.getType().Name\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * If the TreeViewBinding.DataMember property is specified and \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * specified a matching value, this element may get displayed\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public string Type{ get{ return this.GetType().Name; }}\r\n\u00a0\u00a0\u00a0 }\r\n}\r\n\u00a0\r\n<\/pre>\n<p>I added the name of the select stored procedure to the object as a static property. The public fields consumed by the .NET infrastructure, including those mapped to column names, are all properties, because you otherwise risk &#8220;<a href=\"http:\/\/forums.asp.net\/p\/1005999\/2823502.aspx#2823502\">Could not find a property named&#8230;<\/a>&#8221; errors.<\/p>\n<p>We&#8217;re now ready to code a HierarchicalDataSourceView.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System.Configuration;\r\nusing System.Data.SqlClient;\r\nusing System.Data;\r\nusing System.Collections;\r\nusing System.Web.UI;\r\nusing System;\r\nnamespace SQLTreeView\r\n{\r\n\u00a0\u00a0\u00a0 \/\/ The SQLTreeNodeView class encapsulates the\r\n\u00a0\u00a0\u00a0 \/\/ capabilities of the SQLTreeNodeDataSource data source control.\r\n\u00a0\u00a0\u00a0 public class SQLTreeNodeView : HierarchicalDataSourceView\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public string RootNodePath;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected HierarchicalEnumerable _lastreturned = null;\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** create a view of the node with the \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * specified path and its descendants\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public SQLTreeNodeView(string rootNodePath)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Store the root node path\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RootNodePath = rootNodePath;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** create a view of the entire tree\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public SQLTreeNodeView() : this(null) { }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/**Return the list of nodes descending from the slash-delimited path\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * Changing or generalizing the delimiter is \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * left as an exercise to the reader\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public override IHierarchicalEnumerable Select()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 HierarchicalEnumerable retval = new HierarchicalEnumerable();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ load some TreeNodeData objects from the database and return them\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DataSet treeNodeDataSet = new DataSet(SQLTreeNodeData.Select);\r\n\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0try\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* pull a connection string from an \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * configuration\/connectionStrings\/add element in Web.config*\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SqlConnection connect = new SqlConnection(ConfigurationManager\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .ConnectionStrings[\"Connectionstring\"].ConnectionString);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 connect.Open();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SqlCommand command = connect.CreateCommand();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ populate the treeview with descendants of Tag\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 command.CommandText = SQLTreeNodeData.Select;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 command.CommandType = CommandType.StoredProcedure;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SqlCommandBuilder.DeriveParameters(command);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* DataBinding passes an empty string instead of null to \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * HierarchicalDataSourceView.GetHierarchicalView. Correct that \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * here.*\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 command.Parameters[\"@Path\"].Value = this.RootNodePath == \"\" ? \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 null : this.RootNodePath;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Hashtable parentage = new Hashtable();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SqlDataReader reader = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 command.ExecuteReader(CommandBehavior.Default);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 while (reader.Read())\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ capture the record\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SQLTreeNodeData thd = new SQLTreeNodeData(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (string)reader[\"Path\"], (string)reader[\"Text\"], \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (string)reader[\"ToolTip\"], (string)reader[\"ImageURL\"], \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (bool)reader[\"Checked\"]);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ store the record in your hashtable\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 string path = reader[\"Path\"].ToString();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parentage.Add(path, thd);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* the stored procedure guarantees that parents will be \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * returned before children\r\n\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0*\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 string parentpath = reader[\"ParentPath\"].ToString();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ add the parent and child to each node\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 thd.Parent = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (SQLTreeView.SQLTreeNodeData)parentage[parentpath];\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (thd.Parent != null) \/\/ handle the root\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ((ArrayList)thd.Parent.Children).Add(thd);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ return the root node\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (0 == retval.Count) retval.Add(thd);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 reader.Close();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 reader.Dispose();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 command.Dispose();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 connect.Close();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Write SQLExceptions to standard out\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 catch (SqlException se)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Console.Write(se.StackTrace);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 foreach (SqlError ser in se.Errors)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Console.Write(ser.Message);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 catch (NullReferenceException nre)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* return an empty data set if the \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 connectionstringname property isn't present\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ update the local field\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 _lastreturned = retval;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Return the HierarchicalEnumerable with 0 or 1 members \r\n\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return retval;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Report the last data to be constructed and returned\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public HierarchicalEnumerable LastReturned { get { return _lastreturned; } }\r\n\u00a0\u00a0\u00a0 }\r\n}\r\n<\/pre>\n<p>The Select method builds the hierarchy and loads the root node into the return value. It also loads every other node into that node&#8217;s parent&#8217;s Children collection, which we defined as part of the SQLTreeNodeData implementation of IHierachyData. That Select method is the most coding that gets done; most of this implementation is just getting the objects in the right places.<\/p>\n<p>Unlike with identifiers like the NodeID, there&#8217;s no philosophical objection to exposing <b>lft<\/b> and <b>rgt<\/b> to the data consumer. They&#8217;re meaningful numbers describing the node position in any context. We certainly could have retrieved the data as <b>lft<\/b> , <b>rgt<\/b> , Text, et cetera, instead of building the path enumeration in the database, but the logic for matching parents to children in C# would have been somewhat hairier. Any node can get its immediate children from the TreeNode table with<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"lang:c# theme:vs2012\">SELECT * FROM TREENODE WHERE lft &gt; @lft AND rgt &lt; @rgt AND  \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 @LEVEL + 1 = (SELECT COUNT(*) FROM TreeNode anc WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 anc.lft &lt; TREENODE.lft AND anc.rgt &gt; TREENODE.rgt )\r\n<\/pre>\n<p>where @lft is the node&#8217;s <b>lft<\/b> value and @rgt is the node&#8217;s <b>rgt<\/b> value, and @LEVEL is the number of the node&#8217;s ancestors, or<\/p>\n<pre class=\"lang:c# theme:vs2012\">SELECT COUNT(*) FROM TreeNode anc WHERE anc.lft &lt; @lft AND anc.rgt &gt; @rgt \r\n<\/pre>\n<p>but you can see that that&#8217;s starting to become a large number of database round-trips. This is why we&#8217;re getting all the nodes at once.<\/p>\n<p>Or, nearly. There&#8217;s a hidden feature in the code as written that lets you start your tree from any node. Wiring that in to the front-end code is left as an exercise to the reader.<\/p>\n<p>With a principle of reducing impact on data resources in mind, you might prefer to load the data into a detached data set and close the connection before doing anything else at all. However, in this case, all that&#8217;s being done is the building of a number of ArrayList objects &#8212; there&#8217;s little faster. Building the hierarchy of SQLTreeNodeData objects as you navigate the open cursor is not even necessarily more demanding that the construction of an offline DataSet would be.<\/p>\n<p>Our only remaining component to provide a data source for a TreeView is the DataSource itself. So&#8230;<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System.Web.UI;\r\nnamespace SQLTreeView\r\n{\r\n\u00a0\u00a0\u00a0 \/**Data Source for the SQLTreeNodeView\r\n\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0 public class SQLTreeNodeDataSource : HierarchicalDataSourceControl\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 private SQLTreeNodeView _view = null;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Returns a SQLTreeNodeView object providing access to \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * Select functionality for the SQLTreeNode objects descending from the\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * given path\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * @rootnodepath the path from which returned nodes descend; if set to \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * null, all nodes are returned\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected override HierarchicalDataSourceView \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GetHierarchicalView(string rootnodepath)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (null == _view)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 _view = new SQLTreeNodeView(rootnodepath);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else if (_view.RootNodePath != rootnodepath) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 _view.RootNodePath = rootnodepath;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return _view;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Overriding HierarchicalDataSourceControl.CreateControlCollection\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * allows the TreeView to attach to the DataSouce in the aspx page, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * which simplifies data binding immensely\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected override ControlCollection CreateControlCollection()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return new ControlCollection(this);\r\n\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** Return the last data set to be constructed \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public HierarchicalEnumerable LastReturned \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 { get { return _view == null ? null : _view.LastReturned; } }\r\n\u00a0\u00a0\u00a0 }\r\n}\r\n<\/pre>\n<p>We now have a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.ui.hierarchicaldatasourcecontrol(VS.80).aspx\">HierarchicalDataSourceControl<\/a> object that can be used declaratively, so let&#8217;s take advantage of that.<\/p>\n<h3>The Front End Bit<\/h3>\n<p>With a registered and included SQLTreeNodeDataSource control, my aspx page now looks like<\/p>\n<pre class=\"lang:xhtml theme:github mark:1,3,4\">&lt;%@ Page Language=\"C#\" AutoEventWireup=\"true\" \r\nCodeBehind=\"SQLTreeNodeDemonstration.aspx.cs\" \r\nInherits=\"SQLTreeView.SQLTreeNodeDemonstration\" %&gt;\r\n&lt;%@ Register Assembly=\"SQLTreeView\" Namespace=\"SQLTreeView\" TagPrefix=\"stv\" %&gt;\r\n&lt;!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD XHTML 1.0 Transitional\/\/EN\" \r\n\"http:\/\/www.w3.org\/TR\/xhtml1\/DTD\/xhtml1-transitional.dtd\"&gt;\r\n&lt;html xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\" &gt;\r\n&lt;head runat=\"server\"&gt;\r\n\u00a0\u00a0\u00a0 &lt;title&gt;SQLTreeNodeDataSource Demonstration&lt;\/title&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n\u00a0\u00a0\u00a0 &lt;form id=\"frm\" runat=\"server\"&gt;\r\n\u00a0\u00a0\u00a0 &lt;stv:SQLTreeNodeDataSource runat=\"server\" id=\"tds\" \/&gt;\r\n\u00a0\u00a0\u00a0 &lt;div&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;asp:TreeView ID=\"tvw\" runat=\"server\" DataSourceID=\"tds\"\/&gt;\r\n\u00a0\u00a0\u00a0 &lt;\/div&gt;\r\n\u00a0\u00a0\u00a0 &lt;\/form&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;\r\n\r\n<\/pre>\n<p>All that&#8217;s left, really, is to bind the S<b>QLTreeNodeData<\/b> fields to <b>TreeNode<\/b> properties, which we do through a property page on the <b>TreeView<\/b> called <b>DataBindings<\/b>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Third.png\" alt=\"623-Third.png\" width=\"213\" height=\"408\" \/><\/p>\n<p class=\"caption\">Figure \u00a03&#8230; Property Page for the TreeView Control<\/p>\n<p>Click on the ellipses in the Property Page for the <b>TreeView<\/b> to launch the<b> TreeViewDataBindingsEditor.<\/b><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Fourth.png\" alt=\"623-Fourth.png\" width=\"513\" height=\"406\" \/><\/p>\n<p class=\"caption\">Figure 4&#8230;TreeViewDataBindings Editor<\/p>\n<p>Click the &#8216;Add&#8217; button to create a <b>TreeViewDataBindings <\/b>object.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Fifth.png\" alt=\"623-Fifth.png\" width=\"513\" height=\"406\" \/><\/p>\n<p class=\"caption\">Figure\u00a0 5&#8230; TreeViewBindings Editor with bindings defined<\/p>\n<div class=\"float-left\">\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Sixth.png\" alt=\"623-Sixth.png\" width=\"258\" height=\"574\" \/><\/p>\n<p class=\"caption\">Figure \u00a06&#8230; ASPX page showing TreeView <br \/>\nwith HierarchicalDataSourceControl<\/p>\n<\/div>\n<p>Populate the fields of the new TreeViewDataBindings. The DataMember field should be the return value of the &#8216;Type&#8217; property of the SQLTreeNodeData objects. This can be useful as a filter, but as I&#8217;ve implemented it each object just returns the class name, so this field could just as well be left blank. The ImageUrlField, TextField and ValueField have the obvious mappings, and the ValueField is set to text so as to enable the Find method for nodes. This is also why it was so important that we use a declarative data source with DataSourceID instead of programmatically setting the DataSource property of the TreeView &#8212; Find does not work otherwise, and we are going to need it to.\u00a0 Not to foreshadow.<\/p>\n<p>You now have a working TreeView based on hierarchical data stored as nested sets in the database.<\/p>\n<p>We should take a moment to savor this before addressing that nagging feeling that we&#8217;ve defined a column and returned it in our stored procedure without displaying it on the page.<\/p>\n<h3>Adding Checkboxes<\/h3>\n<div class=\"float-left\">\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Seventh.png\" alt=\"623-Seventh.png\" width=\"213\" height=\"408\" \/><\/p>\n<p class=\"caption\">Figure 7: ShowCheckBoxes property of <br \/>\nthe TreeView control<\/p>\n<\/div>\n<p>There comes a time in every .NET implementation &#8212; or at least every one I have done to date &#8212; where patiently sorting out how to connect all the plumbing to meet your specified requirements suddenly blows up into a red-eyed tragicomedy. One day you&#8217;re tending sheep for Laban, then suddenly you&#8217;re taking blows to your hip socket in a tent at Peniel.<\/p>\n<p>And so it is with checkboxes. You can show them easily enough by setting &#8216;ShowCheckBoxes&#8217; to Leaf, All or whatever you please.<\/p>\n<p>The difficulties &#8212; or &#8216;opportunities for excellence&#8217; &#8212; start to come when you try to do things like handle OnCheck events, or even set them to checked. I will show you how to do the latter\u00a0 here. The former is still beyond my reach.<\/p>\n<h4>OnDataBound<\/h4>\n<p>.NET data-bound controls provide &#8216;OnDataBinding&#8217; and &#8216;OnDataBound&#8217; events, to modify states before or after a control updates itself with retrieved data respectively. We can implement any event handler by clicking through the event part of the property page.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Eighth.png\" alt=\"623-Eighth.png\" width=\"213\" height=\"408\" \/><\/p>\n<p class=\"caption\">Figure\u00a0 8&#8230; Event handlers for the TreeView control<\/p>\n<p>Triple click in the method name field for the DataBound event handler, and Visual Studio 2005 will create the handler and give it focus in the code editor.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing System.Web.UI;\r\nnamespace SQLTreeView\r\n{\r\n\u00a0\u00a0\u00a0 public partial class SQLTreeNodeDemonstration : System.Web.UI.Page\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0protected void tvw_DataBound(object sender, EventArgs e)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ get the last constructed data set from the data source\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 HierarchicalEnumerable he = tds.LastReturned;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 foreach (SQLTreeNodeData tnd in he)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CheckBoxes(tnd);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/** For each passed node and its descendants, check the corresponding \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * box if the Checked property is true\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 protected void CheckBoxes(SQLTreeNodeData tnd) { \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ if Checked is True, find the corresponding TreeNode and check it\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/* Note that FindNode is expensive enough that \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * tvw.FindNode(tnd.Path).Checked = tnd.Checked \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 * would be rather slower\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 *\/\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (tnd.Checked) tvw.FindNode(tnd.Path).Checked = true;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 foreach (IHierarchyData ihd in tnd.GetChildren())\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CheckBoxes((SQLTreeNodeData)ihd);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<div class=\"float-left\">\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/623-Ninth.png\" alt=\"623-Ninth.png\" width=\"276\" height=\"567\" \/><\/p>\n<p class=\"caption\">Figure\u00a0 9&#8230; Displayed TreeView with checked state <br \/>\nfrom data<\/p>\n<\/div>\n<p>This justifies our <b>LastReturned<\/b> read-only HierarchicalEnumerable-valued property procedures for the <b>SQLTreeNodeView<\/b> and <b>SQLTreeNodeDataSource<\/b> objects. If we hadn&#8217;t get a copy of those objects when creating them for the TreeView&#8217;s consumption, we would have to return to the database to get the data again. Our other option would be to store the &#8216;Checked&#8217; value in some otherwise irrelevant property, perhaps binding it with <b>TargetField,<\/b> but we have to realize that the days of storing obscure XML snippets in the tag property of controls are gone.<\/p>\n<p>In any case, we have now succeeded in our task as set.<\/p>\n<p>Thanks for reading, and I hope you enjoy your many .NET TreeView controls populated from nested sets!<\/p>\n<p>&nbsp;<\/p>\n<\/div>\n<div>\u00a0<\/div>\n","protected":false},"excerpt":{"rendered":"<p>It sometimes happens that Web Server controls that visualize data<br \/>\ndon&#8217;t quite fit with the way that the data is actually held in the application. This shouldn&#8217;t be an obstacle, as Ion Freeman<br \/>\npoints out. To prove his point, he shows how to use Nested Sets with Treeview.&hellip;<\/p>\n","protected":false},"author":221850,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,4157,4179],"coauthors":[11315],"class_list":["post-493","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-asp-net","tag-source-control"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/493","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221850"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=493"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/493\/revisions"}],"predecessor-version":[{"id":74322,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/493\/revisions\/74322"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=493"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=493"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=493"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=493"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}