Finally, I got it! :-) No more limitations on navigation ever, ever, ever....
Thought i should share this with my fellow coders here at ERT for input, output and whatever.. please feel free to comment ..
This code does the following:
Lets you expand and collapse nodes as you prefer.
Lets you navigate to whatever page (Also pages unlisted in the SiteMap or DB) across your site without no trouble.
Maintains the state of the treeview.
Lets you get rid of those ridiculous + and - expand/collapse icons.
I used the OnSelectedNodeChanged event, and ShowExpandCollapse attrib of the treeview to make this work.
This is the treeview code for a master page:
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="xmlDataSource" OnSelectedNodeChanged="TreeView1_SelectedNodeChanged"
ExpandDepth="0" MaxDataBindDepth="10" PopulateNodesFromClient="False" ShowExpandCollapse="False"
OnDataBound="TreeView1_DataBound">
</asp:TreeView>
Codebehind:
protected void Page_Load(object sender, EventArgs e)
{
// Datastuff for binding the XML source or whatever here:
....
// Disable ExpandDepth if the TreeView's expand/collapse
// state is stored in session.
if (Session["TreeViewState"] != null)
TreeView1.ExpandDepth = -1;
}
public void TreeView1_SelectedNodeChanged(object sender, EventArgs e)
{
//If the selected node is changed and we are working with the top node (If you want the ExpandCollapse func further dow, duplicate this part..)
if (TreeView1.SelectedNode.Depth == 0)
{
TreeView1.CollapseAll(); //Collapse all nodes
TreeView1.SelectedNode.Expand(); node//Expand the selected node
}
//Save the state of the treeview
if (IsPostBack)
{
List<string> list = new List<string>(16);
SaveTreeViewState(TreeView1.Nodes, list);
Session["TreeViewState"] = list;
}
//All done, lets redirect to the new page
if (IsPostBack)
{
// Here i use the "value" attribute of the treeview node, this must be used instead of the NavigateURL attribute whitch makes the
//OnSelectedNodeChange event break in the first place
string fw = string.Empty;
fw = TreeView1.SelectedValue.ToString();
Response.Redirect(fw.ToString());
}
}
//Save or reapply the state of the treeview
protected void TreeView1_DataBound(object sender, EventArgs e)
{
if (Session["TreeViewState"] == null)
{
// Record the TreeView's current expand/collapse state.
List<string> list = new List<string>(16);
SaveTreeViewState(TreeView1.Nodes, list);
Session["TreeViewState"] = list;
}
else
{
// Apply the recorded expand/collapse state to the TreeView.
List<string> list = (List<string>)Session["TreeViewState"];
RestoreTreeViewState(TreeView1.Nodes, list);
}
}
//The save state func...
private void SaveTreeViewState(TreeNodeCollection nodes, List<string> list)
{
// Recursivley record all expanded nodes in the List.
foreach (TreeNode node in nodes)
{
if (node.ChildNodes != null && node.ChildNodes.Count != 0)
{
if (node.Expanded.HasValue && node.Expanded == true && !String.IsNullOrEmpty(node.Text))
list.Add(node.Text);
SaveTreeViewState(node.ChildNodes, list);
}
}
}
//The restore state func...
private void RestoreTreeViewState(TreeNodeCollection nodes, List<string> list)
{
foreach (TreeNode node in nodes)
{
// Restore the state of one node.
if (list.Contains(node.Text))
{
if (node.ChildNodes != null && node.ChildNodes.Count != 0 && node.Expanded.HasValue && node.Expanded == false)
node.Expand();
}
else
{
if (node.ChildNodes != null && node.ChildNodes.Count != 0 && node.Expanded.HasValue && node.Expanded == true)
node.Collapse();
}
// If the node has child nodes, restore their state, too.
if (node.ChildNodes != null && node.ChildNodes.Count != 0)
RestoreTreeViewState(node.ChildNodes, list);
}
}
Credits:
Much of the code and ideas where borowed from Jeff Prosises MSDN Wicked Code article here:
http://msdn.microsoft.com/msdnmag/issues/06/06/WickedCode/default.aspx(DotNET and Atlas example code included in article, worth a look@)
Happy coding! [cool]
For those of you that want the Data and xsl code, here it is:
Just as you know it, this way of doing it does not support breadcrumbs, so you might want to do a SiteMapDataSource thingey instead.
(I build my own custom breadcrumbs from the DataSource, but thats another chapter..)
Treeview DataBindings:
<DataBindings>
<asp:TreeNodeBinding DataMember="MenuItem" TextField="Text" ToolTipField="ToolTip" PopulateOnDemand="True" SelectAction="SelectExpand"
ValueField="MyVal" />
</DataBindings>
The Datasource build:
DataSet ds = new DataSet();
ConnectionStringSettings cs = ConfigurationManager.ConnectionStrings["DatabaseConnectionString1"];
using (SqlConnection conn = new SqlConnection(cs.ConnectionString))
{
string sql = "Select MenuID, Text, Description, ParentID, Page from Menu";
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
da.Fill(ds);
da.Dispose();
}
ds.DataSetName = "Menus";
ds.Tables[0].TableName = "Menu";
DataRelation relation = new DataRelation("ParentChild",
ds.Tables["Menu"].Columns["MenuID"],
ds.Tables["Menu"].Columns["ParentID"],
true);
relation.Nested = true;
ds.Relations.Add(relation);
xmlDataSource.Data = ds.GetXml();
The xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="
http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<!-- Find the root node called Menus and call MenuListing for its children -->
<xsl:template match="/Menus">
<MenuItems>
<xsl:call-template name="MenuListing" />
</MenuItems>
</xsl:template>
<!-- Allow for recusive child node processing -->
<xsl:template name="MenuListing">
<xsl:apply-templates select="Menu" />
</xsl:template>
<xsl:template match="Menu">
<MenuItem>
<!-- Convert Menu child elements to MenuItem attributes -->
<xsl:attribute name="Text">
<xsl:value-of select="Text"/>
</xsl:attribute>
<xsl:attribute name="ToolTip">
<xsl:value-of select="Description"/>
</xsl:attribute>
<xsl:attribute name="MyVal">
<xsl:value-of select="Page"/>
<xsl:text>?Menu=</xsl:text>
<xsl:value-of select="MenuID"/>
</xsl:attribute>
<!-- Call MenuListing if there are child Menu nodes -->
<xsl:if test="count(Menu) > 0">
<xsl:call-template name="MenuListing" />
</xsl:if>
</MenuItem>
</xsl:template>
</xsl:stylesheet>
(The nicecoloured version of this is available here:
http://forums.asp.net/thread/1387445.aspx)