AMissico
AMissico

Reputation: 21684

How do I find a TreeViewItem with a specific value in its Tag property

I am trying to find a specific TreeViewItem whose Tag property is set to a specific value. The below FindNode only works for the first level items or in other levels if the parent TreeViewItem is expanded. In the below example, if "FFF" is expanded, then FindNode works as expected. I am assuming that ContainerFromItem is returning null because the items have not been created. Is there a way to force the creation of all the TreeViewItems?

    <TreeView x:Name="__items">

        <TreeViewItem Header="AAA"
                      Tag="{x:Static my:Node.A}" />

        <TreeViewItem Header="BBB"
                      Tag="{x:Static my:Node.B}">

            <!-- Items will be added later. -->

        </TreeViewItem>

        <TreeViewItem Header="CCC"
                      Tag="{x:Static my:Node.C}" />

        <TreeViewItem Header="DDD"
                      Tag="{x:Static my:Node.D}" />

        <TreeViewItem Header="EEE"
                      Tag="{x:Static my:Node.E}" />

        <TreeViewItem Header="FFF"
                      Tag="{x:Static my:Node.F}">

            <TreeViewItem Header="GGG"
                          Tag="{x:Static my:Node.G}" />

            <TreeViewItem Header="HHH"
                          Tag="{x:Static my:Node.H}" />

        </TreeViewItem>

        <TreeViewItem Header="III"
                      Tag="{x:Static my:Node.I}" />

    </TreeView>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(MainWindow_Loaded);    
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        TreeViewItem a = FindNode(__items.ItemContainerGenerator, __items.Items, Node.H); 
    }

    private TreeViewItem FindNode(ItemContainerGenerator gen, ItemCollection items, Node value)
    {
        TreeViewItem oResult = null;

        foreach (var oItem in items)
        {
            TreeViewItem oTreeViewItem = (TreeViewItem)gen.ContainerFromItem(oItem);

            if (oTreeViewItem == null) { continue; }

            if ((Node)oTreeViewItem.Tag == value) { oResult = oTreeViewItem; break; }

            if (oTreeViewItem.Items.Count > 0)
            {
                oResult = FindNode(oTreeViewItem.ItemContainerGenerator, oTreeViewItem.Items, value);

                if (oResult != null) { break; }
            }
        }
        return oResult;
    }

}

public enum Node { A, B, C, D, E, F, G, H, I, J, }

Based on hbarck's answer the correct FindNode implementation is:

    private TreeViewItem FindNode(ItemCollection items, Node value)
    {
        TreeViewItem oResult = null;

        foreach (var oItem in items)
        {
            TreeViewItem oTreeViewItem = (TreeViewItem)oItem;

            if ((Node)oTreeViewItem.Tag == value) { oResult = oTreeViewItem; break; }

            if (oTreeViewItem.Items.Count > 0)
            {
                oResult = FindNode(oTreeViewItem.Items, value);

                if (oResult != null) { break; }
            }
        }
        return oResult;
    }

Upvotes: 2

Views: 4846

Answers (2)

hbarck
hbarck

Reputation: 2944

the easiest way to make sure that your TreeViewItems exist is probably to set IsExpanded="True" on each of them, and to set IsVirtualizing to False on the TreeView. Just out of curiosity: what happens if you don't use the ItemGenerator, but just iterate the Items collection directly instead? Since you're not using a DataTemplate, I'd imagine that the Items collection should contain the hardcoded items from your XAML file.

I've done the following test:

<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <TreeView x:Name="TestTreeView">
        <TreeViewItem Header="Item 1">
            <TreeViewItem Header="Item 1 1"/>
            <TreeViewItem Header="Item 1 2"/>
        </TreeViewItem>
        <TreeViewItem Header="Item 2"/>
    </TreeView>
    <Button x:Name="TestButton" Click="TestButton_Click">Test</Button>
</StackPanel>
</Window>

and code behind:

Class MainWindow 

    Private Sub TestButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)

        Stop
        For Each t As TreeViewItem In Me.TestTreeView.Items
            Debug.Print("Item: {0}, Child count:{1}", t.Header, t.Items.Count)
        Next
    End Sub
End Class

The output in the debug window is

Item: Item 1, Child count:2
Item: Item 2, Child count:0

which means that the items are instantiated and can be iterated, even in the child levels. Probably what gets into your way is the ItemContainerGenerator, which is actually only needed when you are using DataTemplates. Also, you probably have to wait until after the Loaded event of the window in order to have all items instantiated.

Upvotes: 0

Martin Moser
Martin Moser

Reputation: 6269

Yes you can force the creation of Items in an ItemsControl. Access the ItemContainerGenerator, then (this is the magic ;)) cast it to IItemContainerGenerator, as the interface is implemented explicit. Using StartAt and GenerateNext allows you to force the items to be created.

see: Why does ItemContainerGenerator return null? You just need to do it for all items.

Upvotes: 1

Related Questions