Click here to Skip to main content
15,886,648 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
I'm having a mental block right now, this can't be that hard.
I have a function which should get the last visible node in the TreeView. First I want to go to the first visible node and then second from there on go as long as node.IsVisible().

What I have tried:

C#
public TreeNode GetLastVisibleNode()
    {
        var node = treeControl1.Nodes.Cast<TreeNode>().Where(x => x.IsVisible).FirstOrDefault();
        TreeNode retVal = node;
        while (node != null && node.IsVisible)
        {
            if (!node.IsSelected)
                retVal = node;
            node = node.NextVisibleNode;
        }
        return retVal;
    }


I made it to iterate to the last visible node when I have the first visible (HOORAY) but my approach of getting the first visible is WRONG (OHHHHH!)
I noticed that treeControl1.Nodes only gives me the parent nodes but obvioulsy I want to get the first visible node of all nodes.
I also know that I probably need a recursive method but as I said in the beginning, I'm having a mental block right now and want to have this fixed quite soon :(
Posted
Updated 19-Jul-18 4:39am

The following should work:

private static TreeNode FindFirstVisible(TreeNodeCollection nodes)
{
  foreach (TreeNode node in nodes)
  {
    if (node.IsVisible)
      return node;

    TreeNode first = FindFirstVisible(node.Nodes);
    if (first != null)
      return first;
  }

  return null;
}

To call, use as follows:

TreeNode first = FindFirstVisible(tree.Nodes);

For completeness, you may also want to check out (which is slightly different from what you ask):
TreeView.TopNode Property (System.Windows.Forms) | Microsoft Docs[^]

Actually, thinking a bit longer, you shouldn't even need to dig into the tree. I believe for a child node to be visible the parent must be visible. So, I think you either must have a top level node visible, or no nodes are visible. So, the following should work:

private static TreeNode FindFirstVisible(TreeNodeCollection nodes) =>
  nodes.Cast<TreeNode>().FirstOrDefault(node => node.IsVisible);

Based on subsequent information, and if you are concerned about scrolling, the following methods will let you select the last partially or fully visible node. Using NextVisibleNode will not consider whether the node has scrolled out of range.
private TreeNode GetFirstVisibleNode(TreeView tree, bool includePartial)
{
  if (!includePartial)
    return tree.TopNode;

  Rectangle treeBounds = tree.ClientRectangle;
  return tree.Nodes.Cast<TreeNode>()
    .FirstOrDefault(node => node.IsVisible &&
      treeBounds.Contains(node.Bounds));
}

private TreeNode GetLastVisibleNode(TreeView tree, bool includePartial)
{
  TreeNode last = GetFirstVisibleNode(tree, includePartial);
  Rectangle treeBounds = tree.Bounds;

  for (TreeNode node = last; node != null; node = node.NextVisibleNode)
  {
    if (includePartial)
    {
      if (!treeBounds.IntersectsWith(node.Bounds))
        break;
    }
    else
    {
      if (!treeBounds.Contains(node.Bounds))
        break;
    }

    last = node;
  }

  return last;
}
 
Share this answer
 
v5
Comments
[no name] 19-Jul-18 10:06am    
First of all: I accidentally rated your question with one star :( I'm very sorry for that!

Second: I've found a different solution which works very good so I didn't test yours
[no name] 19-Jul-18 10:15am    
You can easily correct your vote by clicking the 5'th star :-)
Eric Lynch 19-Jul-18 10:17am    
No problem. You may want to read through the answer. If you're looking for the first truly visible node (IsVisible=true and not scrolled out of view), then the TreeView.TopNode is the best way to go. Otherwise, don't bother with recursion, just examine the top level nodes, one is guaranteed to be the first visible node, if any nodes in the tree are visible. That's a simple bit of LINQ, presented at the end of my solution. Also, regarding the vote, I think you may be able to edit your choice...I forget. No problem either way.
[no name] 19-Jul-18 10:35am    
Thank you for showing me the TreeView.TopNode property! I used this to get to the first one and then basically did the same what I did before (which you can see in my Question) with a slightly different semantics.

Also I now was able to edit my vote (couldn't do it before, that's why I apologized). I'll mark your answer as accepted and also add a third answer with what I did now which is way more easy to read for me as a newbie in C#.
Eric Lynch 19-Jul-18 12:12pm    
One other thing you may want to consider, a node can have IsVisible = true and still not be visible, because it is scrolled out of range. Regrettably, NextVisibleNode does not consider scrolling. I updated the solution to include methods that consider scrolling, in case this is part of your requirements.
C#
public TreeNode GetLastVisibleNode()
        {
            return treeControl1.Nodes.Cast<TreeNode>().Select(GetLastVisibleNode).
                            LastOrDefault(first => first != null);
        }

TreeNode GetLastVisibleNode(TreeNode parentNode) =>
        parentNode.IsVisible
             ? parentNode
             : parentNode.Nodes.Cast<TreeNode>().Select(GetLastVisibleNode).
                      LastOrDefault(childFirstNode => childFirstNode != null);

Someone answered the same question I asked here
 
Share this answer
 
v2
I was told in the comments that TreeView.TopNode gets me the first visible node.

From there on I just iterated through the list as long as node.IsVisible and node != null.
C#
public TreeNode GetLastVisibleNode()
        {
            TreeNode node = treeControl1.TopNode;
            TreeNode retVal;
            do
            {
                retVal = node;
                node = node.NextVisibleNode;
            } while (node != null && node.IsVisible);

            return retVal;
        }
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900