[Universal Apps] How to: Find DataTemplate-Generated Elements

Category: windows phone howto

Question

Alexander Deeb on Wed, 21 May 2014 09:54:23


There is already a Microsoft tutorial for doing this in WPF and older versions of the .NET Framework. This tutorial can be found here: http://msdn.microsoft.com/en-us/library/bb613579(v=vs.110).aspx

As I was working on a universal app for Windows 8.1 and Windows Phone 8.1, I noticed that this tutorial doesn't quite work because the System.Windows library is used for DataTemplates, not the Windows.UI.Xaml library that is standard in universal apps. Adding a using statement for the System.Windows library didn't seem to work, so I was stuck. Luckily, I've managed to figure out the solution. Let's take this data template as an example:

<ListBox Name="lstBox">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Name="grd">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>

                    <TextBox Name="txtBox" Grid.Column="0" Text="{Binding Path=text}" />
                    <Button Name="btn" Grid.Column="1" Content="test" />
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

Using Microsoft's tutorial, you might retrieve the Button using C# like so:

// Get the ListBoxItem at the specified index in the ListBox
ListBoxItem ListBoxItem = (ListBoxItem)lstBox.ItemContainerGenerator.ContainerFromIndex(0);

// Get the ContentPresenter of the ListBoxItem
ContentPresenter ContentPresenter = FindVisualChild<ContentPresenter>(ListBoxItem);

// Get the DataTemplate of the ContentPresenter
DataTemplate ListBoxItemTemplate = ContentPresenter.ContentTemplate;
            
// Find the Button by name from the DataTemplate set on the ContentPresenter
Button btn = (Button)ListBoxItemTemplate.FindName("btn", ContentPresenter);

Most of the same code will work in universal apps, but if you notice, the DataTemplate here is a System.Windows DataTemplate. In universal apps, you will be working with a Windows.UI.Xaml DataTemplate. The latter differs in the methods and properties that it lets you access, most notably the FindName method: it's gone! There are no methods or properties that The Windows.UI.Xaml DataTemplate provides us with to access an exact reference to that Button of ours (if you're wondering, the LoadContent method just creates a copy of the data template for use on another control).

Fortunately, Microsoft has introduced a new property to the ContentControl object: ContentTemplateRoot. This property "Gets the root element of the date (should be data?) template specified by the ContentTemplate property." This means that this property will return the first item in our DataTemplate, or our Grid. After we retrieve our Grid, we can just search its children for the Button, either by name or by index. The result? Shorter code! Here's an example:

// Get the first ListBoxItem in the ListBox
ListBoxItem ListBoxItem = (ListBoxItem)lstBox.ContainerFromIndex(0);
            
// Get the DataTemplate's Grid with its child elements
Grid TheGrid = (Grid)ListBoxItem.ContentTemplateRoot;
            
// Get the child Button by index
Button TheButton = (Button)TheGrid.Children[1];

// Get the child Button by name
Button SameButton = (Button)TheGrid.FindName("btn");

Of course, I showed off both the index way of retrieving the Button and the name way of retrieving the Button for demonstration purposes only. You would only need to use one of the methods. I prefer the index way because I believe it is better for performance.

Well, that's about it! I couldn't find any resources online that addressed this topic, so I thought I'd make a quick post about it. Please let me know if you have any questions or know of a better way to do this!



Replies

Jeff Sanders on Wed, 21 May 2014 18:15:43


Thanks for sharing!