Get TreeViewItem

Ken Krugh 116 Reputation points
2020-05-22T04:44:17.777+00:00

I have a virtualized TreeView that I was building "manually" by creating a stack panel and using .Add for each item. I then had a very simple _TextInput event that was finding the TreeViewItem and passing it to these two clever routines that would bring the item into view. Full disclosure, these are NOT my originals:

Private Function FindVisualChild(Of T As System.Windows.Media.Visual)(ByVal visual As System.Windows.Media.Visual) As T  
    For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(visual) - 1  
        Dim child As System.Windows.Media.Visual = CType(VisualTreeHelper.GetChild(visual, i), System.Windows.Media.Visual)  
        If child IsNot Nothing Then  
            Dim correctlyTyped As T = TryCast(child, T)  
            If correctlyTyped IsNot Nothing Then  
                Return correctlyTyped  
            End If  
            Dim descendent As T = FindVisualChild(Of T)(child)  
            If descendent IsNot Nothing Then  
                Return descendent  
            End If  
        End If  
    Next  
    Return Nothing  
End Function  
  
Sub BringIntoView(ByVal item As TreeViewItem)  
    Dim parent As ItemsControl = TryCast(item.Parent, ItemsControl)  
    If parent IsNot Nothing Then  
        Dim itemHost As System.Windows.Controls.VirtualizingStackPanel = FindVisualChild(Of System.Windows.Controls.VirtualizingStackPanel)(parent)  
        If itemHost IsNot Nothing Then  
            itemHost.BringIndexIntoViewPublic(parent.Items.IndexOf(item))  
            item.Focus()  
        End If  
    End If  
End Sub  

(why is the code formatting not working?)

Now that I've bound the data (make the tree load in a fraction of the time) the TreeView.TreeViewItem is the type of my data object and not a TreeViewItem, which is handy elsewhere in the code except that I need the TreeViewItem for these two routines to bring into view the virutualized TreeViewItem.

I've spent considerable time finding all kinds of posts with similar questions but haven't come to any answers that don't involve turn OFF virtualization, which isn't an option due to the number of items in the TreeView. This Microsoft post seems to have the trick where the tree and then it's child items get passed recursively as containers, but that involves traversing the whole tree, which isn't necessary because I know the index I need.

The Note in the post mentions searching the data items then "finding" the corresponding TreeViewItem. Searching my data is the easy part (don't need to search child items) and I know the index of the TreeViewItem I want but I can't work out how to get the actual TreeViewItem object.

Any help would be greatly appreciated.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,670 questions
{count} votes

Accepted answer
  1. Ken Krugh 116 Reputation points
    2020-05-24T16:22:44.257+00:00

    Ugh, sorry, I may have misled earlier. I didn't see anywhere in the code in the MS post actually seemed to get the TreeViewItem, it was getting the VirtualizingStackPanel and bringing it into view and (if I'm remembering) selected the TreeViewItem. Also, I've not yet looked up the difference in them all, but that code uses CType and TryCast instead of DirectCast.

    Hauling bricks for more yard work this morning (amazing how a little yard work fixes my programming problems face-palm) it dawned on me that the item I'm looking for is now in view! AFTER calling the BringInfoView routine I can now just use ItemContainerGenerator.ContainerFromIndex to get the item the user was searching for selected!

    So, to recap, keeping in mind that I am NOT dealing with child items, if you have the index of the item you want, call the BringIntoView (which calls FindVisualChild) like this, which then allows you to get a reference to the TreeViewItem and select it:

    'TView is the tree you're working with, i is the index
    Call BringIntoView(TView, i)
    'Now that bring into view made it visible you can get the TreeViewItem and select it
    TVItm = TView.ItemContainerGenerator.ContainerFromIndex(i)
    TVItm.IsSelected = True
    

    The Sub and Function:

    Private Function FindVisualChild(Of T As System.Windows.Media.Visual)(ByVal visual As System.Windows.Media.Visual) As T
        For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(visual) - 1
            Dim child As System.Windows.Media.Visual = CType(VisualTreeHelper.GetChild(visual, i), System.Windows.Media.Visual)
            If child IsNot Nothing Then
                Dim correctlyTyped As T = TryCast(child, T)
                If correctlyTyped IsNot Nothing Then
                    Return correctlyTyped
                End If
                Dim descendent As T = FindVisualChild(Of T)(child)
                If descendent IsNot Nothing Then
                    Return descendent
                End If
            End If
        Next
        Return Nothing
    End Function
    
    Sub BringIntoView(TheTreeView As TreeView, TheIdx As Integer)
        Try
            Dim parent As ItemsControl = TryCast(TheTreeView, ItemsControl)
            If parent IsNot Nothing Then
                Dim itemHost As System.Windows.Controls.VirtualizingStackPanel = FindVisualChild(Of System.Windows.Controls.VirtualizingStackPanel)(parent)
                If itemHost IsNot Nothing Then
                    itemHost.BringIndexIntoViewPublic(TheIdx)
                End If
            End If
        Catch ex As Exception
            MsgBox("Error in BringIntoView: " & ex.Message & vbCrLf & "TheIdx: " & TheIdx)
        End Try
    End Sub
    

    On another note while I have your ear, Peter. I've added this TreeView.ItemContainerStyle to the TreeView XAML and an IsExp property to the FontSetData class so that when I change the items in the TreeView I can have the ones that were expanded stay expanded when the refresh happens.

    <TreeView.ItemContainerStyle>
       <Style TargetType="{x:Type TreeViewItem}">
          <Setter Property="IsExpanded" Value="{Binding IsExp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </Style>
    </TreeView.ItemContainerStyle>
    

    Becuase the FntData doesn't have this IsExp property I'm seeing a bunch of errors saying BindingExpression path error: 'IsExp' property not found on 'object' ''FntData', which makes sense.

    My question is what's the best practice to handle that? Do I simply add that property to the FntData class or is there a way to exclude it in the XAML?

    Thank you again, Peter!


3 additional answers

Sort by: Most helpful
  1. Ken Krugh 116 Reputation points
    2020-05-23T14:48:14.847+00:00

    Getting the TreeViewItem is actually where I'm stuck.

    Prior to binding, my simple search went through the TreeViewItems looking at the Content of what was then a label, child 1 of the TreeViewItem Header (I've since changed to a TextBlock because I saw that's what you used in the code you sent, Peter, and saw they were "lighter"), when it matched what was typed on the keyboard it passed that TreeViewItem to the BringIntoView routine posed above, which worked for bringing it into view. Code was this:

            TView = sender  
            If TView.SelectedItem Is Nothing Then  
                SItm = 0  
            Else  
                SItm = TView.Items.IndexOf(TView.SelectedItem)  
            End If  
            e.Handled = True  
            TView.Tag &= e.Text  
            For i = SItm To TView.Items.Count - 1  
                TVItm = TView.Items(i)  
                If TVItm.Header.Children(1).Content.Substring(0, TView.Tag.ToString.Length).ToUpper = TView.Tag.ToString.ToUpper Then  
                    TVItm.IsSelected = True  
                    Call MiscUtils.BringIntoView(TVItm)  
                    Exit Sub  
                End If  
            Next i  
    
      
    

    I was hoping to use that same BringIntoView routine by simply passing the TreeViewItem associated with the data item the code now finds, but getting the TreeViewItem from the data item is proving the trick. I found things that work as long as I first view/build the thing I'm looking for, which you allude to above and just isn't practical.

    I THOUGHT this Microsoft post (also linked earlier) is supposed to do exactly what I need, get the TreeViewItem from the data item even if it hasn't yet been displayed. I've tried converting it to VB in hopes of learning how (if?!) it works but haven't been able to make it work as of yet.

    Am I mistaken about what that post is supposed to do?

    Thanks AGAIN for taking the time, Peter.

    0 comments No comments

  2. Ken Krugh 116 Reputation points
    2020-05-23T19:21:19.897+00:00

    Had an "ah-ha!" moment while mowing the lawn, am posting again so you don't spend any wasted time, Peter.

    I got the Microsoft code to work, it took a full 3 minutes for it to select the thing 1/2 way through the TreeView but it worked! x-)

    I realized that at least some of the code I'd been using likely came from that Microsoft post and was able to modify the BringIntoView routine I'd been using to accept the TreeView and the index to bring into view. Modified it to look like this:

        Sub BringIntoView(TheTreeView As TreeView, TheIdx As Integer)
            Try
                Dim parent As ItemsControl = TryCast(TheTreeView, ItemsControl)
                If parent IsNot Nothing Then
                    Dim itemHost As System.Windows.Controls.VirtualizingStackPanel = FindVisualChild(Of System.Windows.Controls.VirtualizingStackPanel)(parent)
                    If itemHost IsNot Nothing Then
                        itemHost.BringIndexIntoViewPublic(TheIdx)
                        'item.Focus()
                    End If
                End If
            Catch ex As Exception
                MsgBox("Error in BringIntoView: " & ex.Message & vbCrLf & "TheIdx: " & TheIdx)
            End Try
        End Sub
    

    The commented "Focus" line was where the item would get selected before I modified it to work with the index instead of a TreeViewItem.

    Now, though, it seems I have the same but opposite problem as before! In that routine itemHost is the Virtulaizing stack panel that's brought into view. Now I need to get back (up a level, so to speak?) to the TreeViewItem to be able to select it.

    Or, is there another way to select it? Oy!

    Thanks again,
    Ken


  3. Ken Krugh 116 Reputation points
    2020-05-29T13:37:11.047+00:00

    Well, the simple answer to my errors problem was that I hadn't added the inheritance to the FntData class when I created the FntBase for the binding. [face-palm]

    Thanks for the attached behavior code, I THINK I'm understanding just what's going on. This stuff is so cool, hope to look at some online classes sometime, or maybe just hire you to write the next app! :o)

    Thanks again for all your great help.

    0 comments No comments