"Remi Hugnon" <r.******@voila.fr> wrote in message
news:40***********************@news.easynet.fr...
Hi,
I want to build an expandable treeview. I saw some code on VB but I know
only C#
I plan for my class that each nodes has its own id (using span for
example).
Into a single aspx page without any other control, it's very easy because
I play on URL and parameters and refresh my aspx.
But in a screen having 2 treeview, I have the param and other controls.
How can I organize my object in order to catch (meaning server side) wich node
had been clicked ?
Ok, I've got an example. It makes this post a bit long, but I've also
included a zip file with the sources.
The example consists of three classes, DemoTreeView, DemoTreeViewNode and
DemoTreeViewNodeCollection:
DemoTreeView Class declaration:
/// <summary>
/// A simple Tree View control as an example for Remi Hugnon
(r.******@voila.fr)
/// </summary>
[DefaultProperty("Text"),
ToolboxData("<{0}:DemoTreeView
runat=server></{0}:DemoTreeView>")]
[ParseChildren(true, "DemoTreeViewNodes")]
public class DemoTreeView : WebControl, IPostBackEventHandler
Note the use of the ParseChildren attribute. This allows the page parser to
interpret the content inside of the DemoTreeView as items to be added to the
DemoTreeNodes property. Each element will be parsed and then added to the
DemoTreeNodes collection via IList.Add.
IPostBackEventHandler is implemented in order to provide a Click event when
a node is clicked.
DemoTreeViewNodes property:
#region Public Properties
/// <summary>
/// Gets the top-level nodes in the tree
/// </summary>
[Category("Behavior"),
Description("The top-level nodes in the tree"),
DesignerSerializationVisibility(DesignerSerializat ionVisibility.Content),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerDefaultProper ty)]
public DemoTreeViewNodeCollection DemoTreeViewNodes
{
get
{
if (_nodes == null)
_nodes = new DemoTreeViewNodeCollection();
return _nodes;
}
}
#endregion
This is a simple implementation of a read-only property. The interesting
things are in the attributes. The PersistenceMode is set to match the
ParseChildren attribute on the class. DesignerSerializationVisibility is set
to cause the property to be persisted as content between the start and end
tags.
#region Rendering
/// <summary>
/// Renders the contents of the control - what's between the
beginning and ending tags
/// </summary>
/// <param name="writer">The HtmlTextWriter to render to</param>
protected override void RenderContents(HtmlTextWriter writer)
{
DemoTreeViewNodeCollection nodes = DemoTreeViewNodes;
RenderNodes(writer, nodes, string.Empty, 0);
base.RenderContents(writer);
}
private void RenderNodes(HtmlTextWriter writer,
DemoTreeViewNodeCollection nodes, string prefix, int depth)
{
for (int i = 0; i < nodes.Count; i++)
{
DemoTreeViewNode node = nodes[i];
string path;
if (prefix.Length == 0)
path = i.ToString();
else
path = string.Format("{0}|{1}", prefix, i);
writer.AddAttribute(HtmlTextWriterAttribute.Onclic k,
Page.GetPostBackEventReference(this, path));
writer.AddStyleAttribute(HtmlTextWriterStyle.Curso r,
"hand");
writer.AddStyleAttribute(HtmlTextWriterStyle.Left,
string.Format("{0}em", (depth*2)));
writer.AddStyleAttribute(HtmlTextWriterStyle.Posit ion, "relative");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.Write(node.Text);
writer.RenderEndTag();
RenderNodes(writer, node.DemoTreeViewNodes, path,
depth+1);
}
}
#endregion
Because I allow a DemoTreeViewNode to contain other nodes, the control has
to render recursively. The RenderNodes method takes care of that. It goes
through each node in the collection, calculates its path and position, and
then renders it. Each node is rendered as a div element with an OnClick
handler. The handler is what does the postback, passing the path of the
node.
Note that the div ends before the inner nodes are rendered. I found this
necessary so that each node could be clicked. Otherwise, only the outermost
div would receive clicks.
IPostBackEventHandler implementation:
#region IPostBackEventHandler Members
/// <summary>
/// Raise the appropriate event, given the postback data
/// </summary>
/// <param name="eventArgument">The string path to the node
posting back</param>
public void RaisePostBackEvent(string eventArgument)
{
if (eventArgument != null)
{
Page.Trace.Write("RaisePostBackEvent",
eventArgument);
OnClick(new
DemoTreeViewNodeEventArgs(DemoTreeViewNodes[eventArgument]));
}
}
#endregion
RaisePostBackEvent is called upon a postback. The eventArgument parameter
receives the path of the node. This simply raises the Click event of the
DemoTreeView, passing the referenced node. The DemoTreeViewNodeCollection
indexer does the hard work of locating the node.
DemoTreeViewNode Class Declaration:
/// <summary>
/// A node in the DemoTreeView
/// </summary>
[ParseChildren(true, "DemoTreeViewNodes")]
[ToolboxItem(false)]
public class DemoTreeViewNode
At present, the nodes are very simple, so they don't need to derive from
Control or WebControl.
Note the ParseChildren attribute, which is identical to the attribute on the
DemoTreeView class. This permits nodes to contain other nodes. ToolboxItem
is set to false so that the class doesn't show up in the toolbox.
Public Properties
The property implementation is so simple that I won't show it. The
DemoTreeViewNodes property is identical to the same property implemented in
DemoTreeView. The Text property is a simple string property with no
ViewState support.
DemoTreeViewNodeCollection:
This is a simple, strongly-typed collection of DemoTreeViewNode instances.
The only fancy part is the implementation of a second indexer. This indexer
accepts a pipe-separated list of integer node offsets and locates the
appropriate node. For instance, "1|2|0" would return
demoTreeView.DemoTreeViewNodes[1].DemoTreeViewNodes[2].DemoTreeViewNodes[0].
Test page:
<%@ Register TagPrefix="cc1" Namespace="JWS.WebInfrastructure.WebControls"
Assembly="JWS.WebInfrastructure.WebControls"%>
<%@ Page Language="C#" %>
<script runat="server">
void DemoTreeView1_Click(object sender,
JWS.WebInfrastructure.WebControls.DemoTreeView.Dem oTreeViewNodeEventArgs e)
{
Label1.Text = string.Format("Node clicked - {0}", e.Node.Text);
}</script>
<html>
<body>
<form id="form1" runat="server">
<div>
<cc1:DemoTreeView ID="DemoTreeView1" Runat="server"
OnClick="DemoTreeView1_Click">
<cc1:DemoTreeViewNode Text="treeViewNode5">
<cc1:DemoTreeViewNode Text="treeViewNode5.1">
</cc1:DemoTreeViewNode>
</cc1:DemoTreeViewNode>
<cc1:DemoTreeViewNode Text="treeViewNode6">
<cc1:DemoTreeViewNode Text="treeViewNode6.1">
<cc1:DemoTreeViewNode Text="treeViewNode6.1.0">
</cc1:DemoTreeViewNode>
</cc1:DemoTreeViewNode>
</cc1:DemoTreeViewNode>
</cc1:DemoTreeView>
<asp:Label ID="Label1" Runat="server" Text="Label">
</asp:Label>
</div>
</form>
</body>
</html>
So, the way this all works is that each node renders as:
<div onclick="__doPostBack('TreeView1','1|0|0')"
style="cursor:hand;left:4em;position:relative;">
treeViewNode6.1.0
</div>
When clicked, IPostBackEventHandler.RaisePostBackEvent will be called with
"1|0|0" as an argument. The indexer will locate node "treeViewNode6.1.0",
and will raise the Click event, passing that node in the EventArgs. The
Click event handler will fire and display the Text property of the node in
the label.
I hope that helps and isn't too long. Any questions, please ask.
--
John Saunders
johnwsaundersiii at hotmail