Maintain scroll position of treeview

I'm not a VB guy but in C# I do it this way:

Some Win32 native functions:

[DllImport("user32.dll",  CharSet = CharSet.Unicode)]
public static extern int GetScrollPos(IntPtr hWnd, int nBar);

[DllImport("user32.dll",  CharSet = CharSet.Unicode)]
public static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);

private const int SB_HORZ = 0x0;
private const int SB_VERT = 0x1;

A method which returns a point for the current scroll position:

private Point GetTreeViewScrollPos(TreeView treeView)
{
    return new Point(
        GetScrollPos(treeView.Handle, SB_HORZ), 
        GetScrollPos(treeView.Handle, SB_VERT));
}

A method to set the scroll position:

private void SetTreeViewScrollPos(TreeView treeView, Point scrollPosition)
{
    SetScrollPos(treeView.Handle, SB_HORZ, scrollPosition.X, true);
    SetScrollPos(treeView.Handle, SB_VERT, scrollPosition.Y, true); 
}

Then when you update your tree, do the following:

BeginUpdate();
Point ScrollPos = GetTreeViewScrollPos(treeMain);
// write your update code here
SetTreeViewScrollPos(treeMain, ScrollPos);
EndUpdate();

I think I figured it out:

  1. Get the node at the top of the treeview.
  2. Expand the parent node.
  3. Make the node that was previously at the top visible.
If treeNodeParent.IsExpanded = False Then
    Dim currentNode As TreeNode = TreeViewHosts.GetNodeAt(0, 0)
    treeNodeParent.Expand()
    currentNode.EnsureVisible()
End If

Is the a better way to do this?


Another way you can preserve the scroll position without external functions is using the TopNode property of the tree...

TopNode gets or sets the first fully-visible tree node in the tree view control.

If you just want to expand a node and have it preserve the top node:

TreeNode topNode = m_Tree.TopNode;
treenode.Expand();
m_Tree.TopNode = topNode;

Otherwise, if you are rebuilding a tree (such as refreshing a file structure), you can use the following method...

Before Clearing the tree, store the full path to the top node:

string topNodePath = null;
TreeNode topNode = null;
if (m_Tree.TopNode != null)
{
    topNodePath = m_Tree.TopNode.FullPath;
}

m_Tree.Clear();

After adding a nodes, check its FullPath against the topNodePath:

nodes.Add(node)
if ((topNodePath != null) && (node.FullPath == topNodePath))
{
    topNode = node;
}

After adding all nodes, update the tree's TopNode property:

if (topNode != null)
{
    m_Tree.TopNode = topNode;
}

I use a similar technique for selected and expanded nodes. SelectedNode works almost exactly as TopNode shown above. For expanded nodes I use a recursive function to loop through the child nodes and add the path of expanded nodes to a list. Then expands them based on their path after the children have been added.

Of course, if you have a lot of sibling nodes with the same name, this might not work as well :-)