Advertisement:

Skystone Software

http://www.SkystoneSoftware.com

Scott Waletzko's Blog
Custom TreeNode Collapser
Published: 10/23/2007
XMl / RSS

Here's an interesting little class I threw together (very quickly - use at your own risk, and definitely test it out) to handle a problem with a new enterprise system I am writing. We're using a completely customized TreeView control, and one of the requirements is that top-level nodes be collapsable. That is to say, if you have three nodes in a row, the user should be able to collapse those nodes into one node, and then re-expand them afterwards. I wrote a control that handles this for you, you just pass it the first node in the group and the number of nodes to collapse, and it does the rest.

Here's the class:

VB:
Public Class TreeNodeCollapser

	#Region "Module-level Variables"

	Private m_clsCollapsedNode As TreeNode
	Private m_sCollapsedNodeText As String
	Private m_iCollapsedNodeImageIndex As Integer
	Private m_iCollapsedNodeSelectedImageIndex As Integer
	Private m_clsCollapsedNodes As System.Collections.Generic.List(Of TreeNode)

	#End Region
	
	#Region "Class Constructors"

		Public Sub New(ByVal CollapsedNodeText As String)
			Me.m_sCollapsedNodeText = CollapsedNodeText
		End Sub

		Public Sub New(ByVal CollapsedNodeText As String, ByVal CollapsedNodeImageIndex As Integer)
			Me.New(CollapsedNodeText)
			Me.m_iCollapsedNodeImageIndex = CollapsedNodeImageIndex
			Me.m_iCollapsedNodeSelectedImageIndex = CollapsedNodeImageIndex
		End Sub

		Public Sub New(ByVal CollapsedNodeText As String, ByVal CollapsedNodeImageIndex As Integer, ByVal CollapseNodeSelectedImageIndex As Integer)
			Me.New(CollapsedNodeText, CollapsedNodeImageIndex)
			Me.m_iCollapsedNodeSelectedImageIndex = CollapseNodeSelectedImageIndex
		End Sub

		#End Region
		
		#Region "Properties"

		Public Property CollapsedNodeText() As String
			Get
				Return m_sCollapsedNodeText
			End Get
			Set(ByVal value As String)
				m_sCollapsedNodeText = value
			End Set
		End Property

		Public Property CollapsedNodeImageIndex() As Integer
			Get
				Return m_iCollapsedNodeImageIndex
			End Get
			Set(ByVal value As Integer)
				m_iCollapsedNodeImageIndex = value
			End Set
		End Property

		Public Property CollapsedNodeSelectedImageIndex() As Integer
			Get
				Return m_iCollapsedNodeSelectedImageIndex
			End Get
			Set(ByVal value As Integer)
				m_iCollapsedNodeSelectedImageIndex = value
			End Set
		End Property

		#End Region
		
		Public Function CollapseNodes(ByVal FirstNode As TreeNode, ByVal SubsequentNodeCount As Integer) As TreeNode
			' avoid painting...
			FirstNode.TreeView.SuspendLayout()
			FirstNode.TreeView.BeginUpdate()
			' make new node to represent the collapsed group...
			m_clsCollapsedNode = New TreeNode(Me.CollapsedNodeText, Me.CollapsedNodeImageIndex, Me.CollapsedNodeSelectedImageIndex)
			' store each node to be collapsed locally...
			m_clsCollapsedNodes = New List(Of TreeNode)()
			Dim clsTemp As TreeNode = FirstNode
			Do While Not clsTemp Is Nothing
				m_clsCollapsedNodes.Add(clsTemp)
				If m_clsCollapsedNodes.Count = SubsequentNodeCount + 1 Then
					clsTemp = Nothing
				Else
					clsTemp = clsTemp.NextNode
				End If
			Loop
			' get node collection...
			Dim clsNodes As TreeNodeCollection = Nothing
			If Not FirstNode.Parent Is Nothing Then
				clsNodes = FirstNode.Parent.Nodes
			Else
				clsNodes = FirstNode.TreeView.Nodes
			End If
			' add collapsed node to treeview...
			clsNodes.Insert(FirstNode.Index, m_clsCollapsedNode)
			' remove other nodes...
			For Each clsTemp2 As TreeNode In m_clsCollapsedNodes
				clsNodes.Remove(clsTemp2)
			Next clsTemp2
			' add this item as the tag to the temp node...
			m_clsCollapsedNode.Tag = Me
			' resume painting...
			m_clsCollapsedNode.TreeView.ResumeLayout()
			m_clsCollapsedNode.TreeView.EndUpdate()
			' return...
			Return m_clsCollapsedNode
		End Function

		Public Sub ExpandNodes()
			' validate...
			If m_clsCollapsedNode Is Nothing Then
			Throw New System.Exception("You cannot call ExpandNodes until you call CollapseNodes.")
			End If
			' avoid painting...
			Me.m_clsCollapsedNode.TreeView.SuspendLayout()
			Me.m_clsCollapsedNode.TreeView.BeginUpdate()
			' get node collection...
			Dim clsNodes As TreeNodeCollection = Nothing
			If m_clsCollapsedNode.Parent Is Nothing Then
				clsNodes = m_clsCollapsedNode.TreeView.Nodes
			Else
				clsNodes = m_clsCollapsedNode.Parent.Nodes
			End If
			' replace all nodes in the treeview...
			For Each clsNode As TreeNode In m_clsCollapsedNodes
				clsNodes.Insert(m_clsCollapsedNode.Index, clsNode)
			Next clsNode
			' remove the collapsed node...
			clsNodes.Remove(m_clsCollapsedNode)
			' resume painting...
			clsNodes(0).TreeView.ResumeLayout()
			clsNodes(0).TreeView.EndUpdate()
			' clear variables...
			m_clsCollapsedNode = Nothing
			m_clsCollapsedNodes = Nothing
		End Sub
		
End Class	
	
C#:
public class TreeNodeCollapser
{

    #region Module-level Variables

    private TreeNode m_clsCollapsedNode;
    private string m_sCollapsedNodeText;
    private int m_iCollapsedNodeImageIndex;
    private int m_iCollapsedNodeSelectedImageIndex;
    private System.Collections.Generic.List<TreeNode> m_clsCollapsedNodes;

    #endregion

    #region Class Constructors

    public TreeNodeCollapser(string CollapsedNodeText)
    {
        this.m_sCollapsedNodeText = CollapsedNodeText;
    }

    public TreeNodeCollapser(string CollapsedNodeText, int CollapsedNodeImageIndex)
        : this(CollapsedNodeText)
    {
        this.m_iCollapsedNodeImageIndex = CollapsedNodeImageIndex;
        this.m_iCollapsedNodeSelectedImageIndex = CollapsedNodeImageIndex;
    }

    public TreeNodeCollapser(string CollapsedNodeText, int CollapsedNodeImageIndex, int CollapseNodeSelectedImageIndex)
        : this(CollapsedNodeText, CollapsedNodeImageIndex)
    {
        this.m_iCollapsedNodeSelectedImageIndex = CollapseNodeSelectedImageIndex;
    }

    #endregion

    #region Properties

    public string CollapsedNodeText
    {
        get { return m_sCollapsedNodeText; }
        set { m_sCollapsedNodeText = value; }
    }

    public int CollapsedNodeImageIndex
    {
        get { return m_iCollapsedNodeImageIndex; }
        set { m_iCollapsedNodeImageIndex = value; }
    }

    public int CollapsedNodeSelectedImageIndex
    {
        get { return m_iCollapsedNodeSelectedImageIndex; }
        set { m_iCollapsedNodeSelectedImageIndex = value; }
    }

    #endregion

    public TreeNode CollapseNodes(TreeNode FirstNode, int SubsequentNodeCount)
    {
        // avoid painting...
        FirstNode.TreeView.SuspendLayout();
        FirstNode.TreeView.BeginUpdate();
        // make new node to represent the collapsed group...
        m_clsCollapsedNode = new TreeNode(this.CollapsedNodeText, this.CollapsedNodeImageIndex, this.CollapsedNodeSelectedImageIndex);
        // store each node to be collapsed locally...
        m_clsCollapsedNodes = new List<TreeNode>();
        TreeNode clsTemp = FirstNode;
        while (clsTemp != null)
        {
            m_clsCollapsedNodes.Add(clsTemp);
            if (m_clsCollapsedNodes.Count == SubsequentNodeCount + 1)
                clsTemp = null;
            else
                clsTemp = clsTemp.NextNode;
        }
        // get node collection...
        TreeNodeCollection clsNodes = null;
        if (FirstNode.Parent != null)
            clsNodes = FirstNode.Parent.Nodes;
        else
            clsNodes = FirstNode.TreeView.Nodes;
        // add collapsed node to treeview...
        clsNodes.Insert(FirstNode.Index, m_clsCollapsedNode);
        // remove other nodes...
        foreach (TreeNode clsTemp2 in m_clsCollapsedNodes)
            clsNodes.Remove(clsTemp2);
        // add this item as the tag to the temp node...
        m_clsCollapsedNode.Tag = this;
        // resume painting...
        m_clsCollapsedNode.TreeView.ResumeLayout();
        m_clsCollapsedNode.TreeView.EndUpdate();
        // return...
        return m_clsCollapsedNode;
    }

    public void ExpandNodes()
    {
        // validate...
        if (m_clsCollapsedNode == null) throw new System.Exception("You cannot call ExpandNodes until you call CollapseNodes.");
        // avoid painting...
        this.m_clsCollapsedNode.TreeView.SuspendLayout();
        this.m_clsCollapsedNode.TreeView.BeginUpdate();
        // get node collection...
        TreeNodeCollection clsNodes = null;
        if (m_clsCollapsedNode.Parent == null)
            clsNodes = m_clsCollapsedNode.TreeView.Nodes;
        else
            clsNodes = m_clsCollapsedNode.Parent.Nodes;
        // replace all nodes in the treeview...
        foreach (TreeNode clsNode in m_clsCollapsedNodes)
        {
            clsNodes.Insert(m_clsCollapsedNode.Index, clsNode);
        }
        // remove the collapsed node...
        clsNodes.Remove(m_clsCollapsedNode);
        // resume painting...
        clsNodes[0].TreeView.ResumeLayout();
        clsNodes[0].TreeView.EndUpdate();
        // clear variables...
        m_clsCollapsedNode = null;
        m_clsCollapsedNodes = null;
    }

}
	

...and here's how you might use it:

C#:
Public Partial Class frmTestNodeCollapser
	Inherits Form

	Private m_clsCollapser As TreeNodeCollapser = Nothing

	Public Sub New()
		InitializeComponent()
		Me.tvMain.Nodes.Add("Test 1")
		Me.tvMain.Nodes.Add("Test 2")
		Me.tvMain.Nodes.Add("Test A")
		Me.tvMain.Nodes.Add("Test B")
		Me.tvMain.Nodes.Add("Test C")
		Me.tvMain.Nodes.Add("Test 3")
		Me.tvMain.Nodes.Add("Test 4")
		m_clsCollapser = New TreeNodeCollapser("Collapsed Nodes")
	End Sub

	Private Sub btnCollapse_Click(ByVal sender As Object, ByVal e As EventArgs)
		' collapse...            
		Dim clsTemp As TreeNode = m_clsCollapser.CollapseNodes(Me.tvMain.Nodes(2), 2)
		clsTemp.NodeFont = New Font(Me.tvMain.Font, FontStyle.Italic)
		' display...
		Me.btnCollapse.Enabled = False
		Me.btnExpand.Enabled = True
	End Sub

	Private Sub btnExpand_Click(ByVal sender As Object, ByVal e As EventArgs)
		m_clsCollapser.ExpandNodes()
		Me.btnExpand.Enabled = False
		Me.btnCollapse.Enabled = True
	End Sub

End Class
	
C#:
public partial class frmTestNodeCollapser : Form
{

    TreeNodeCollapser m_clsCollapser = null;

    public frmTestNodeCollapser()
    {
        InitializeComponent();
        this.tvMain.Nodes.Add("Test 1");
        this.tvMain.Nodes.Add("Test 2");
        this.tvMain.Nodes.Add("Test A");
        this.tvMain.Nodes.Add("Test B");
        this.tvMain.Nodes.Add("Test C");
        this.tvMain.Nodes.Add("Test 3");
        this.tvMain.Nodes.Add("Test 4");
        m_clsCollapser = new TreeNodeCollapser("Collapsed Nodes");
    }

    private void btnCollapse_Click(object sender, EventArgs e)
    {
        // collapse...            
        TreeNode clsTemp = m_clsCollapser.CollapseNodes(this.tvMain.Nodes[2], 2);
        clsTemp.NodeFont = new Font(this.tvMain.Font, FontStyle.Italic);
        // display...
        this.btnCollapse.Enabled = false;
        this.btnExpand.Enabled = true;
    }

    private void btnExpand_Click(object sender, EventArgs e)
    {
        m_clsCollapser.ExpandNodes();
        this.btnExpand.Enabled = false;
        this.btnCollapse.Enabled = true;
    }

}	
	



Questions or Comments? .

VB to C# and C# to VB translation provided by Instant C# and Instant VB.