user646265
user646265

Reputation: 1031

How to find a TreeView node by its value

I am working on a genealogy application and I plan to store references to each family member in an XML document. I would like to read this document and display it as a treeview in my application and then use it to display a family member elsewhere in the application. What I need to do is find a specific value in the node tree (each member will have a unique value). How would I find this value? Could you please give me a hand here, I am quite new to node editing. TY.

BTW, below is my XAML code:

        <HierarchicalDataTemplate x:Key="NodeTemplate">
        <HierarchicalDataTemplate.ItemsSource>
            <Binding XPath="child::*" />
        </HierarchicalDataTemplate.ItemsSource>
        <TextBlock Text="{Binding Path=Name}" />
    </HierarchicalDataTemplate>
    <XmlDataProvider x:Key="xmlDataProvider"></XmlDataProvider>
</Window.Resources>
<Grid>
    <TreeView Margin="0,24,0,143"
              Name="treeView1"
              Background="AliceBlue"
              ItemsSource="{Binding Source={StaticResource xmlDataProvider}, XPath=*}"
              ItemTemplate= "{StaticResource NodeTemplate}"/>

Upvotes: 0

Views: 3905

Answers (2)

Robert Rossney
Robert Rossney

Reputation: 96730

When you bind a selectable ItemsControl to an XmlDataProvider, the SelectedItem contains the XmlNode that's the binding source for the item. If you set that as the DataContext for a view, you can then display (or edit) anything that can be selected via XPath from that node.

So in the below example, which you can paste into Kaxaml, the ContentControl's DataContext is bound to the SelectedItem on the TreeView. Controls placed inside it can then bind to any XML nodes that are findable via XPath.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Page.Resources>
    <XmlDataProvider x:Key="Data" XPath="/Data">
      <x:XData>
        <Data xmlns="">
          <Person Name="Becky">
            <Person Name="Stephen">
              <Person Name="Tom"/>
              <Person Name="Susannah"/>
            </Person>
            <Person Name="Deborah">
              <Person Name="Geoffrey"/>
              <Person Name="Christopher"/>
            </Person>
          </Person>
        </Data>
      </x:XData>
    </XmlDataProvider>
  </Page.Resources>
  <Grid> 
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <TreeView x:Name="MyTreeView" ItemsSource="{Binding Source={StaticResource Data}, XPath=*}">
      <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding XPath=*}">
          <TextBlock Text="{Binding XPath=@Name}"/>
        </HierarchicalDataTemplate>
      </TreeView.ItemTemplate>
    </TreeView>
    <ContentControl Grid.Column="1" DataContext="{Binding ElementName=MyTreeView, Path=SelectedItem}">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition/>
          <RowDefinition/>
        </Grid.RowDefinitions>

        <Label Grid.Row="0">Parent:</Label>
        <Label Grid.Row="1">Children:</Label>

        <Label Grid.Column="1" Content="{Binding XPath=@Name}"/>
        <ListBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding XPath=*}">
          <ListBox.ItemTemplate>
            <DataTemplate>
              <TextBlock Text="{Binding XPath=@Name}"/>
            </DataTemplate>
          </ListBox.ItemTemplate>
        </ListBox>
      </Grid>
    </ContentControl>
  </Grid>
</Page>

You have to use a ContentControl and set the DataContext. That's the only actual trick here. The reason you have to do this: if you tried to just bind a TextBlock, say, you'd end up with a control like:

<TextBlock Text="{Binding ElementName=MyTreeView, Path=SelectedItem, XPath=@Name}"/>

and you can't use Path and XPath in the same binding.

Upvotes: 0

vortexwolf
vortexwolf

Reputation: 14037

I've assumed that the unique value is the id attribute. So the code will look so:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var item = FindItemById(treeView1.Items.Cast<XmlElement>(), "2.1");
        if (item != null)
        {
            //Do something...
            //for example, item.SetAttribute("name", "test");

            //But this code will not work if the item isn't visible
            //var container = treeView1.ItemContainerGenerator.ContainerFromItem(item);
        }
    }

    public XmlElement FindItemById(IEnumerable<XmlElement> items, string id)
    {
        foreach (var item in items)
        {
            if (item.HasAttribute("id") && item.GetAttribute("id") == id)
                return item;
            var childItemsResult = FindItemById(item.ChildNodes.Cast<XmlElement>(), id);
            if (childItemsResult != null)
                return childItemsResult;
        }

        return null;      
    }

If your xml file has a different attribute as an identifier, change this line accordingly: item.HasAttribute("id") && item.GetAttribute("id").

The final result will be the XmlElement object. But it isn't easy to retrieve the container because of the algorithm of generation of treeview items. Anyway, if you have a specific question - I can help to build a correct architecture.

Upvotes: 1

Related Questions