question

KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 asked ·

Get TreeViewItem

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-wpf
· 2
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi, please, show the code to get the TreeViewItem. Remember, by using a VirtualizingStackPanel as the panel in the TreeView, only the visible TreeViewItems are getting created. You cannot get VirtualizingStackPanel from non visible TreeViewItem and you cannot execute BringIndexIntoViewPublic for non existing TreeViewItem.

1 Vote 1 ·

You probably should only post a single answer and keep editing/improving it.

0 Votes 0 ·
KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 answered ·

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!

· 1 ·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Ken, a good practice is to use an attached behavior and binded property like IsSelected. See my Demo on my OneDrive.

   <TreeView.ItemContainerStyle>
     <Style TargetType="{x:Type TreeViewItem}">
       <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
       <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
       <Setter Property="local:TreeViewBehavior.IsBroughtIntoViewWhenSelected" Value="True"/>
     </Style>
   </TreeView.ItemContainerStyle>


1 Vote 1 ·
KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 answered ·

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.





·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 answered ·

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



· 1 ·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Ken, my demo doesn't work with

     <TreeView
               VirtualizingPanel.IsVirtualizing="True"
 ...


and many nodes in TreeView. In this case ItemContaineGenerator.ContainerFromIndex doesn't get TreeViewItem

   subContainer = DirectCast(container.ItemContainerGenerator.ContainerFromIndex(index), TreeViewItem)

subContainer is Nothing!


0 Votes 0 ·
KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 answered ·

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.

·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.