Introduction
This blog post shows how to use the FindName method to gain access to a template-generated element within a ContentControl. Along the way we will see how to make FrameworkElement’s FindResource method return a reference to a typed DataTemplate (i.e. a DataTemplate in a resource dictionary with no key, but only a DataType value).
Background
The FrameworkTemplate base class has a handy method on it called FindName. You can call FindName on a template, passing it the name of an element in the template and the element on which the template was applied. If the tree of elements generated by the template contains an element with the specified name, FindName returns you a reference to it. If it can’t find the element with the name you specified, it returns null.
The problem
Suppose that we are using a ContentControl to display a data object, and that data object has a DataTemplate applied to it. Also suppose that we need to programmatically interact with one of the elements in the data object’s expanded template. It might seem like a simple task: just call FindName on the DataTemplate which is applied to the data object in the ContentControl, passing in the element name and the ContentControl. However, if you try that approach you will quickly hit the following brick wall:
As you can see above, if you pass the ContentControl into FindName an InvalidOperationException is thrown and the message is “This operation is valid only on elements that have this template applied.” Suddenly this seemingly simple task just got tricky.
The solution
The problem here is that the ContentControl does not directly host the elements provided by the expanded DataTemplate. It uses a ContentPresenter to do the dirty work. If we want to use the FindName method we will have to pass that ContentPresenter as the second argument, instead of the ContentControl. The DataTemplate is applied to the ContentPresenter, not the ContentControl.
Putting the solution to work
The demo project associated with this blog post has a very simple set of functional requirements. When the user clicks on some text, it must rearrange itself, as seen below:
In this demo app we call on the trusty Foo class to be our data object:
Here is the relevant XAML of the demo app’s Window:
The DataTemplate declared in the Window’s resource dictionary is a “typed template,” meaning that it has no x:Key but only a DataType value. Typed templates are automatically applied to instances of the data type with which they are associated. In this case, all Foo objects in the Window will automatically be rendered by that DataTemplate.
The Window contains a ContentControl, whose Content is set to an instance of Foo. When the user clicks on the ContentControl the OnContentControlMouseDown event handling method in the code-behind will be invoked. That method is implemented like so:
The inline comments above should provide sufficient explanation for how the code works, so I won’t reiterate all that here. If you have any questions feel free to drop a comment on this post.
Download the demo app here: FindName with ContentControl (demo project) Be sure to change the file extension from .DOC to .ZIP and then decompress it.
Hey Josh,
Good site you got here. I have a sort of related question. Do you know if its possible to get access to a datatemplate when the window loads and make changes to it? For example say I wanted to change the converter on a binding in the template, is that possible with the current WPF release.
I’m trying to work with FindName, but it actually fails when called in .ctor. I have attached the project, which demonstrates this “feature” (see Window1.xaml.cs file). How can it be fixed?
http://rapidshare.com/files/45905676/TemplateFindName.zip.html
Alex,
You need to wait until the OnApplyTemplate method is invoked. Child classes can override it to perform logic which requires the element’s template to be realized.
Josh
Great thanks!
In fact there is a workaround for this issue. Here is a property which caches an element found in the visual tree:
Well, seems this editor doesn’t eat C# code. 🙂 So if FindName returns null, you can try calling ApplyTemplate() method. Then it is ok.
Thanks Alex. That makes sense.
Josh
I am running into problems like this right now. I am using an ItemsControl and picking off the first item (which is a ContentPresenter). I then get the DataTemplate, and then use the FindName call. Still getting the “This operation is valid only on elements that have this template applied.” exception…
Is there a way to get a reference to a child element in a ControlTemplate without knowing it’s name? What if I don’t know any names and I just want to get the first child of the controltemplate, and maybe I know the first child is a StackPanel.
Trevor,
You can do that with the VisualTreeHelper class.
Josh
Hi Josh,
I have List View whose 2nd column template is selected by the overridden TemplateSelector based on some value in the object bound to that ListViewItem row.Now in the UI correct template gets applied..like column2 of Row1 has Template1 and and Row2 has Template2. Now in Template 2 I have checkbox which I want to Programmatically check if user clicks on say some button. So bacically find all the rows whose column has Template2 and find the checkbox and checkit.. so I have following code in button click handler in xaml.cs file..
for (int i = 0; i < myListView.Items.Count; i++)
{
ListViewItem lvi = (ListViewItem)_myListView.ItemContainerGenerator.ContainerFromIndex(i);
Border bd = (Border)(VisualTreeHelper.GetChild(lvi, 0));
GridViewRowPresenter rp = (GridViewRowPresenter)(VisualTreeHelper.GetChild(bd, 0));
ContentPresenter cell = (ContentPresenter)(VisualTreeHelper.GetChild(rp, 2));
DataTemplate dt = cell.ContentTemplate;
object obj = dt.FindName(“chk1”, cell);
if (null != obj)
{
(obj as CheckBox).IsChecked= true;
}
}
Here though my 2nd row has Template2 applied (having checkbox) still I get the exception “The operation is valid only on element that have this template applied”.. Can you please help?
Basically I get obj as null..
One more thing ..I have tried foloowing as well
ListViewItem lvi = (ListViewItem)myListView.ItemContainerGenerator.ContainerFromIndex(i);
Border bd = (Border)(VisualTreeHelper.GetChild(lvi, 0));
GridViewRowPresenter rp = (GridViewRowPresenter)(VisualTreeHelper.GetChild(bd, 0));
ContentPresenter cell = (ContentPresenter)(VisualTreeHelper.GetChild(rp, 2));
DataTemplate myTemplate1 = (DataTemplate)this.Resources[“CheckCellTemplate”];
CheckBox chk = (CheckBox)myTemplate1.FindName(“chk1”, cell);
if (null != chk )
{
chk.IsChecked = true;
}
here also I get chk as Null..though actually I can see correct template been applied in the UI
I’m looking for the equivilant of Page.Findcontrol in WPF. I have an app with lots of dynamically generated controls. I know their IDs. There are times when I need to find one by the ID and set focus to it. Is there a way to search the control hierarchy of a window like Page.Findcontrol does in a Web UI?
Pat,
You should check out the FindName method. Be sure to read about NameScopes, too.
Josh