Wednesday, March 28, 2012

TreeView in UpdatePanel : Heres a working one

Okay, I've seen this mentioned a bunch of times, people can't get TreeView's working in an update panel. Well I've never had a problem. What I figured out was I used a different methodology to fill my tree. Instead of using the "TreeNodeExpanded" event I use the "TreeNodePopulate" event, and set any node that has children's "PopulateOnDemand" property to true. Here's a full solution that uses your file system as an example (since everyone has one) that works for me (running as a file system site at least since I have admin privie's to my drive.)


Either way it demonstrates the concept, and a working solution that uses a TreeView inside of an UpdatePanel. Hopefully this is helpful. If I'm way off base and this isn't REALLY working let me know, but I have a rather complex dynamically loaded treeview solution that works in a production app, and as far as I can tell it is completly populated server side via ajax postbacks.

<%

@dotnet.itags.org.PageLanguage="C#" %>

<%

@dotnet.itags.org.RegisterAssembly="System.Web.Extensions"Namespace="System.Web.UI"TagPrefix="asp" %>

<%

@dotnet.itags.org.ImportNamespace="System.IO" %>

<!

DOCTYPEhtmlPUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<

scriptrunat="server">

protectedvoid Page_Load(object sender,EventArgs e)

{

if (!Page.IsPostBack)

{

BuildTree();

}

}

protectedvoid BuildTree()

{

tvFiles.Nodes.Clear();

TreeNode root =newTreeNode("Drives",@dotnet.itags.org."0");

root.SelectAction =

TreeNodeSelectAction.None;

root.PopulateOnDemand =

true;

root.Selected =

false;

tvFiles.Nodes.Add(root);

tvFiles.ExpandDepth = 1;

root.Expand();

}

protectedvoid tvFiles_TreeNodePopulate(object sender,TreeNodeEventArgs e)

{

TreeNode parentNode = e.Node;if (parentNode.Value ==@dotnet.itags.org."0")

{

PopulateDrives(parentNode);

}

else

{

PopulateNormal(parentNode);

}

}

protectedvoid PopulateDrives(TreeNode node)

{

string[] drives =Environment.GetLogicalDrives();foreach (string drivein drives)

{

TreeNode driveNode =newTreeNode(drive, drive);

driveNode.SelectAction =

TreeNodeSelectAction.None;

driveNode.Selected =

false;

driveNode.PopulateOnDemand =

true;

node.ChildNodes.Add(driveNode);

}

}

protectedvoid PopulateNormal(TreeNode node)

{

string path = node.Value;try

{

DirectoryInfo directory =newDirectoryInfo(path);foreach (FileSystemInfo childin directory.GetFileSystemInfos())

{

TreeNode childNode =newTreeNode();

childNode.Text = child.Name;

childNode.Value = child.FullName;

if (childisDirectoryInfo)

{

if (((DirectoryInfo)child).GetFileSystemInfos().Length > 0)

{

childNode.PopulateOnDemand =

true;

}

}

node.ChildNodes.Add(childNode);

}

}

catch

{

//HACK: ignore erros, probably security, just don't add the nodes

}

}

</

script>

<

htmlxmlns="http://www.w3.org/1999/xhtml">

<

headrunat="server"><title>Untitled Page</title>

</

head>

<

body><formid="form1"runat="server"><div><asp:ScriptManagerrunat="server"id="scriptManager1"></asp:ScriptManager><asp:UpdatePanelrunat="server"id="treePanel"><ContentTemplate><asp:TreeViewID="tvFiles"runat="server"OnTreeNodePopulate="tvFiles_TreeNodePopulate"></asp:TreeView></ContentTemplate></asp:UpdatePanel>

</div></form>

</

body>

</

html>Nice one.

This is excellent, I was close to dropping the TreeView and rolling my own.

However I now have the problem of persisting the TreeView state accross postbacks. The nodes are being populated from a database through the NodePopulated event. Previously we had a function in NodeExpanded and NodeCollapsed that saved the state to a session variable. Any ideas on the best way to do this now?


On closer inspection this doesn't work as expected. It doesn't appear to make use of the UpdatePanel at all, its just using its PopulateOnDemand feature that was always there. When I have another UpdatePanel on the page and cause a postback inside that, the next attempt to expand a node throws back an error:

Invalid postback or callback argument. Event validation is enabledusing <pages enableEventValidation="true"/> in configuration or<%@. Page EnableEventValidation="true" %> in a page. For securitypurposes, this feature verifies that arguments to postback or callbackevents originate from the server control that originally rendered them.If the data is valid and expected, use theClientScriptManager.RegisterForEventValidation method in order toregister the postback or callback data for validation.

Heres the changed code:

<%@. Page Language="C#" %><%@. Register Assembly="System.Web.Extensions" Namespace="System.Web.UI" TagPrefix="asp" %><%@. Import Namespace="System.IO" %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><script runat="server"> protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { BuildTree(); } } protected void BuildTree() { tvFiles.Nodes.Clear(); TreeNode root = new TreeNode("Drives", @."0"); root.SelectAction = TreeNodeSelectAction.None; root.PopulateOnDemand = true; root.Selected = false; tvFiles.Nodes.Add(root); tvFiles.ExpandDepth = 1; root.Expand(); } protected void tvFiles_TreeNodePopulate(object sender, TreeNodeEventArgs e) { TreeNode parentNode = e.Node; if (parentNode.Value == @."0") { PopulateDrives(parentNode); } else { PopulateNormal(parentNode); } } protected void PopulateDrives(TreeNode node) { string[] drives = Environment.GetLogicalDrives(); foreach (string drive in drives) { TreeNode driveNode = new TreeNode(drive, drive); driveNode.SelectAction = TreeNodeSelectAction.None; driveNode.Selected = false; driveNode.PopulateOnDemand = true; node.ChildNodes.Add(driveNode); } } protected void PopulateNormal(TreeNode node) { string path = node.Value; try { DirectoryInfo directory = new DirectoryInfo(path); foreach (FileSystemInfo child in directory.GetFileSystemInfos()) { TreeNode childNode = new TreeNode(); childNode.Text = child.Name; childNode.Value = child.FullName; if (child is DirectoryInfo) { if (((DirectoryInfo)child).GetFileSystemInfos().Length > 0) { childNode.PopulateOnDemand = true; } } node.ChildNodes.Add(childNode); } } catch { //HACK: ignore erros, probably security, just don't add the nodes } }</script><html xmlns="http://www.w3.org/1999/xhtml" ><head runat="server"><title>Untitled Page</title></head><body><form id="form1" runat="server"><div><asp:ScriptManager runat="server" id="scriptManager1"></asp:ScriptManager><asp:UpdateProgress ID="prog" runat="server"> <ProgressTemplate>Working...</ProgressTemplate></asp:UpdateProgress><asp:UpdatePanel runat="server" id="treePanel" UpdateMode="conditional"><ContentTemplate><asp:TreeView ID="tvFiles" runat="server" OnTreeNodePopulate="tvFiles_TreeNodePopulate"></asp:TreeView><input type="submit" id="btn1" runat="server" value="same panel" /></ContentTemplate></asp:UpdatePanel> <asp:UpdatePanel ID="upd1" runat="server" UpdateMode="conditional"> <ContentTemplate> <input type="submit" id="btn2" runat="server" value="other panel" /> </ContentTemplate> </asp:UpdatePanel> </div></form></body></html>

Thanks badgrs. My current project has the treeview alwaysbeing re-initialized on any updatepanel postback. That is quiteunfortunate. I will need to evaluate wether to use theUpdatePanel now. I may end up doing a part UpdatePanel part"manual" solution where the items that are selected in the TreeView donot use UpdatePanel tech, but custom web service call-backs.

Thanks for the input.

1 comment:

Post a Comment