Finally, I got it! :-) No more limitations on navigation ever, ever, ever....
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);
}
}
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..)
I tried your code and got a System.StackOverflowException on the line "node.Collapse()" in "RestoreTreeViewState()". My TreeView has lots of nodes (about 500). May that be the problem?
I tried doing something similar to a Menu. My menuItems had NavigateUrl properties which I tried to removed and added an OnMenuItemClick handler. In this I used the same lines like Response.Redirect instead of using the NavigateUrl. My menu does not have
any state other than the menuItem selected color change which I have ignored for now. I can see the postback happening and the Response.Redirect working but still the entire Master pageheader (menu resides here) reloads. I have the menu inside a table and
the table is wrapped inside an UpdatePanel.
Hope this helps someone although It doesn't work if you use CSS adapters, well not yet anyway![:)]
Protected Sub Page_Load(ByVal sender
As Object,
ByVal e As System.EventArgs)
' Disable ExpandDepth if the TreeView's expand/collapse
' state is stored in session.
If Session("TreeViewState")
IsNot Nothing
Then
Me.MasterTreeView.ExpandDepth = 0
End If
End Sub
' Save or reapply the state of the treeview
Protected Sub MasterTreeView_DataBound(ByVal sender
As Object,
ByVal e As System.EventArgs)
If Session("TreeViewState")
Is Nothing
Then
' Record the TreeViews current expand/collapse state.
Dim list As ArrayList =
New ArrayList
SaveTreeViewState(MasterTreeView.Nodes, list)
Session(
"TreeViewState") = list
Else
'Apply the recorded expand/collapse state to the TreeView.
Dim list As ArrayList =
CType(Session("TreeViewState"), ArrayList)
RestoreTreeViewState(MasterTreeView.Nodes, list)
End If
End Sub
Protected Sub MasterTreeView_TreeNodeCollapsed(ByVal sender
As Object,
ByVal e As System.Web.UI.WebControls.TreeNodeEventArgs)
If IsPostBack
Then
Dim list As ArrayList =
New ArrayList
SaveTreeViewState(MasterTreeView.Nodes, list)
Session(
"TreeViewState") = list
End If
End Sub
Protected Sub MasterTreeView_TreeNodeExpanded(ByVal sender
As Object,
ByVal e As System.Web.UI.WebControls.TreeNodeEventArgs)
If IsPostBack
Then
Dim list As ArrayList =
New ArrayList
SaveTreeViewState(MasterTreeView.Nodes, list)
Session(
"TreeViewState") = list
End If
End Sub
Private Sub SaveTreeViewState(ByVal nodes
As TreeNodeCollection,
ByVal list As ArrayList)
' Recursivley record all expanded nodes in the List.
For Each node
As TreeNode In nodes
If (node.ChildNodes
IsNot Nothing
And node.ChildNodes.Count <> 0)
Then
If (node.Expanded.HasValue
AndAlso CBool(node.Expanded)
AndAlso _
Not String.IsNullOrEmpty(node.Text))
Then
list.Add(node.Text)
SaveTreeViewState(node.ChildNodes, list)
End If
End If
Next
End Sub
Private Sub RestoreTreeViewState(ByVal nodes
As TreeNodeCollection,
ByVal list As ArrayList)
For Each node
As TreeNode In nodes
' Restore the state of one node.
If list.Contains(node.Text)
Then
If (node.ChildNodes
IsNot Nothing
AndAlso _
(node.ChildNodes.Count <> 0)
AndAlso _
node.Expanded.HasValue
AndAlso _
node.Expanded.GetValueOrDefault(
False))
Then
node.Expand()
End If
ElseIf (node.ChildNodes
IsNot Nothing
AndAlso _
node.ChildNodes.Count <> 0
AndAlso _
node.Expanded.HasValue
AndAlso _
node.Expanded.GetValueOrDefault(
True))
Then
node.Collapse()
End If
' If the node has child nodes, restore their states, too.
If (node.ChildNodes
IsNot Nothing
AndAlso node.ChildNodes.Count <> 0)
Then
Ghanshyam; One trick i have used with menus is to simply append a unique node name to the navigate url, and then walk through the menu and mark the top parent selected.. That will apply your formatting through postbacks.
Ghanshyam; One trick i have used with menus is to simply append a unique node name to the navigate url, and then walk through the menu and mark the top parent selected.. That will apply your formatting through postbacks.
Ghanshyam; One trick i have used with menus is to simply append a unique node name to the navigate url, and then walk through the menu and mark the top parent selected.. That will apply your formatting through postbacks.
maflones
Member
67 Points
23 Posts
Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Sep 01, 2006 07:09 PM|LINK
Finally, I got it! :-) No more limitations on navigation ever, ever, ever....
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>
treeview child nodes monitor master page page_load from child page via parent event accessibility
Mark Evans
Member
43 Points
10 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Sep 07, 2006 06:18 PM|LINK
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
' Disable ExpandDepth if the TreeView's expand/collapse
' state is stored in session.
If ViewState("TreeViewState") IsNot Nothing Then
Me.MasterTreeView.ExpandDepth = -1
End If
End Sub
Protected Sub MasterTreeView_SelectedNodeChanged(ByVal sender As Object, ByVal e As System.EventArgs)
'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 MasterTreeView.SelectedNode.Depth = 0 Then
MasterTreeView.CollapseAll() ' Collapse all nodes
MasterTreeView.SelectedNode.Expand()
End If
' Save the state of the treeview
If IsPostBack Then
Dim list As ArrayList = New ArrayList
SaveTreeViewState(MasterTreeView.Nodes, list)
Session("TreeViewState") = list
End If
' All done, lets redirect to the new page
If IsPostBack Then
' 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
Dim fw As String = String.Empty
fw = MasterTreeView.SelectedValue.ToString
Response.Redirect(fw.ToString)
End If
End Sub
' Save or reapply the state of the treeview
Protected Sub MasterTreeView_DataBound(ByVal sender As Object, ByVal e As System.EventArgs)
If Session("TreeViewState") Is Nothing Then
' Record the TreeViews current expand/collapse state.
Dim list As ArrayList = New ArrayList
SaveTreeViewState(MasterTreeView.Nodes, list)
Session("TreeViewState") = list
Else
'Apply the recorded expand/collapse state to the TreeView.
Dim list As ArrayList = CType(Session("TreeViewState"), ArrayList)
RestoreTreeViewState(MasterTreeView.Nodes, list)
End If
End Sub
'The save state func...
Private Sub SaveTreeViewState(ByVal nodes As TreeNodeCollection, ByVal list As ArrayList)
' Recursivley record all expanded nodes in the List.
For Each node As TreeNode In nodes
If (node.ChildNodes Is Nothing And node.ChildNodes.Count = 0) Then
If node.Expanded.HasValue Or CBool(node.Expanded) Or String.IsNullOrEmpty(node.Text) Then
list.Add(node.Text)
SaveTreeViewState(node.ChildNodes, list)
End If
End If
Next
End Sub
'The restore state func...
Private Sub RestoreTreeViewState(ByVal nodes As TreeNodeCollection, ByVal list As ArrayList)
For Each node As TreeNode In nodes
If list.Contains(node.Text) Then
If (node.ChildNodes Is Nothing) Or (node.ChildNodes.Count = 0) Or node.Expanded.HasValue Or Not CBool(node.Expanded) Then
************Throws an exception on the line above saying the 'node.Expanded' must have a value*****
node.Expand()
End If
Else
If (node.ChildNodes Is Nothing) Or (node.ChildNodes.Count = 0) Or node.Expanded.HasValue Or CBool(node.Expanded) Then
node.Collapse()
End If
End If
If node.ChildNodes Is Nothing Or node.ChildNodes.Count = 0 Then
RestoreTreeViewState(node.ChildNodes, list)
End If
Next
End Sub
</script>
Hope you can help!!!
Cheers
Mark
maflones
Member
67 Points
23 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Sep 08, 2006 11:20 AM|LINK
Hi, it looks like you are trying to rewrite my code there.. I am no VB dude, but one error is obvious.. && does not mean "OR", but "AND"..
[8-|]
mkr
Member
10 Points
2 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Sep 08, 2006 02:10 PM|LINK
ghanashyaml
Member
62 Points
13 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Oct 12, 2006 07:21 AM|LINK
I tried doing something similar to a Menu. My menuItems had NavigateUrl properties which I tried to removed and added an OnMenuItemClick handler. In this I used the same lines like Response.Redirect instead of using the NavigateUrl. My menu does not have any state other than the menuItem selected color change which I have ignored for now. I can see the postback happening and the Response.Redirect working but still the entire Master pageheader (menu resides here) reloads. I have the menu inside a table and the table is wrapped inside an UpdatePanel.
Any idea why it is not working?
Thanks,
Ghanshyam
Mark Evans
Member
43 Points
10 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Nov 17, 2006 04:28 PM|LINK
Correct VB translation should be...
Hope this helps someone although It doesn't work if you use CSS adapters, well not yet anyway![:)]
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
' Disable ExpandDepth if the TreeView's expand/collapse
' state is stored in session. If Session("TreeViewState") IsNot Nothing Then Me.MasterTreeView.ExpandDepth = 0 End If End Sub
' Save or reapply the state of the treeview Protected Sub MasterTreeView_DataBound(ByVal sender As Object, ByVal e As System.EventArgs) If Session("TreeViewState") Is Nothing Then ' Record the TreeViews current expand/collapse state. Dim list As ArrayList = New ArrayListSaveTreeViewState(MasterTreeView.Nodes, list)
Session(
"TreeViewState") = list Else 'Apply the recorded expand/collapse state to the TreeView. Dim list As ArrayList = CType(Session("TreeViewState"), ArrayList)RestoreTreeViewState(MasterTreeView.Nodes, list)
End If End Sub
Protected Sub MasterTreeView_TreeNodeCollapsed(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.TreeNodeEventArgs) If IsPostBack Then Dim list As ArrayList = New ArrayListSaveTreeViewState(MasterTreeView.Nodes, list)
Session(
"TreeViewState") = list End If End Sub Protected Sub MasterTreeView_TreeNodeExpanded(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.TreeNodeEventArgs) If IsPostBack Then Dim list As ArrayList = New ArrayListSaveTreeViewState(MasterTreeView.Nodes, list)
Session(
"TreeViewState") = list End If End Sub
Private Sub SaveTreeViewState(ByVal nodes As TreeNodeCollection, ByVal list As ArrayList) ' Recursivley record all expanded nodes in the List. For Each node As TreeNode In nodes If (node.ChildNodes IsNot Nothing And node.ChildNodes.Count <> 0) Then If (node.Expanded.HasValue AndAlso CBool(node.Expanded) AndAlso _ Not String.IsNullOrEmpty(node.Text)) Thenlist.Add(node.Text)
SaveTreeViewState(node.ChildNodes, list)
End If End If Next End Sub Private Sub RestoreTreeViewState(ByVal nodes As TreeNodeCollection, ByVal list As ArrayList) For Each node As TreeNode In nodes ' Restore the state of one node. If list.Contains(node.Text) Then If (node.ChildNodes IsNot Nothing AndAlso _(node.ChildNodes.Count <> 0)
AndAlso _node.Expanded.HasValue
AndAlso _node.Expanded.GetValueOrDefault(
False)) Thennode.Expand()
End If ElseIf (node.ChildNodes IsNot Nothing AndAlso _node.ChildNodes.Count <> 0
AndAlso _node.Expanded.HasValue
AndAlso _node.Expanded.GetValueOrDefault(
True)) Thennode.Collapse()
End If ' If the node has child nodes, restore their states, too. If (node.ChildNodes IsNot Nothing AndAlso node.ChildNodes.Count <> 0) ThenRestoreTreeViewState(node.ChildNodes, list)
End If Next
End Submaflones
Member
67 Points
23 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Nov 21, 2006 02:16 PM|LINK
maflones
Member
67 Points
23 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Nov 21, 2006 02:16 PM|LINK
maflones
Member
67 Points
23 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Nov 21, 2006 02:17 PM|LINK
maflones
Member
67 Points
23 Posts
Re: Maintaining treeview state across your site in a master page, and no expandcollapse icons.
Nov 21, 2006 02:20 PM|LINK
Mark Evans..
"Correct VB translation should be..."
Excellent!
"Hope this helps someone although It doesn't work if you use CSS adapters, well not yet anyway!
"
Right.. I am working on some adapterstuff now, so if i find a good workaround i will post it!
Thanks for the heads up!