How do I programmatically interact with template-generated elements? Part II

This post shows you how to find a named element within a DataTemplate.

In Part I, we discussed how to find a named element within a ControlTemplate. That was fairly simple; you’d call Template.FindName on the control that the ControlTemplate has been applied to. But if the template is a DataTemplate, then the scenario is a bit more complex. For instance, if you have a data-bound ListBox that uses a DataTemplate, each generated list item has a tree of generated elements (as described by your DataTemplate). In this post, we’ll walk through that scenario and retrieve a named element within the DataTemplate of a certain list item.

Before showing the code that finds the named element, let’s set up our scenario. We have a Button and a ListBox that’s data-bound (if you want to see how this particular ListBox is bound, you can download the attached zip file).

    <Border Margin="15" BorderBrush="Aqua" BorderThickness="2" Padding="8" CornerRadius="5">

      <StackPanel>

        <ListBox Name="myListBox" ItemTemplate="{StaticResource myDataTemplate}"

                 IsSynchronizedWithCurrentItem="True">

          <ListBox.ItemsSource>

            <Binding Source="{StaticResource InventoryData}" XPath="Books/Book"/>

          </ListBox.ItemsSource>

        </ListBox>

        <Button Margin="10"

                Click="DataTemplateFindElement">Get text of textBlock in DataTemplate</Button>

      </StackPanel>

    </Border>

The ListBox uses a simple DataTemplate. The DataTemplate has an element that’s given the name textBlock:

    <DataTemplate x:Key="myDataTemplate">

      <TextBlock Name="textBlock" FontSize="14">

        <TextBlock.Text>

          <Binding XPath="Title"/>

        </TextBlock.Text>

      </TextBlock>  

    </DataTemplate>

This screenshot shows our simple UI:

 

 

Now let’s write the button event handling code so that when we click the button, we retrieve the TextBlock that’s within the DataTemplate of the current list item. To do that, we need to perform the following steps:

1. Get a hold of the current list item:

   // Note that the ListBox must have

   // IsSynchronizedWithCurrentItem set to True for this to work

   ListBoxItem myListBoxItem =

   (ListBoxItem)(myListBox.ItemContainerGenerator.ContainerFromItem(myListBox.Items.CurrentItem));

2. Find the ContentPresenter of that list item by walking through its visual tree [1]:

   ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem);

3. Now you can call FindName on the DataTemplate of that ContentPresenter:

    DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;

    TextBlock myTextBlock = (TextBlock)myDataTemplate.FindName("textBlock", myContentPresenter);

4. Finally, you can do whatever you want to the element you just retrieved. For demonstration purposes, we create a message box to show the content of that the TextBlock:

    MessageBox.Show("The text of the named TextBlock in the DataTemplate of the selected list item: "

        + myTextBlock.Text);

Now we can select an item, click the button, and see the message box that shows the text content of the TextBlock retrieved from the corresponding DataTemplate!

 

 

You can download this project from the attached zip file. Enjoy!

[1] The FindVisualChild method called in step 2:

  private childItem FindVisualChild<childItem>(DependencyObject obj)

  where childItem : DependencyObject

  {

  for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

  {

  DependencyObject child = VisualTreeHelper.GetChild(obj, i);

  if (child != null && child is childItem)

  return (childItem)child;

  else

  {

  childItem childOfChild = FindVisualChild<childItem>(child);

  if (childOfChild != null)

  return childOfChild;

  }

  }

  return null;

  }

FindElementinDataTemplate_C#_VB.zip