How to use FindName with a ContentControl

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:

FindName with ContentControl (error)

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:

FindName with ContentControl (before and after)

In this demo app we call on the trusty Foo class to be our data object:

FindName with ContentControl (Foo class)

Here is the relevant XAML of the demo app’s Window:

FindName with ContentControl (XAML)

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:

FindName with ContentControl (code-behind)

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.

About these ads

14 Responses to How to use FindName with a ContentControl

  1. TJ says:

    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.

  2. Alex Nesterov says:

    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

  3. Josh Smith says:

    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

  4. Alex Nesterov says:

    Great thanks!

  5. Alex Nesterov says:

    In fact there is a workaround for this issue. Here is a property which caches an element found in the visual tree:

  6. Alex Nesterov says:

    Well, seems this editor doesn’t eat C# code. :) So if FindName returns null, you can try calling ApplyTemplate() method. Then it is ok.

  7. Josh Smith says:

    Thanks Alex. That makes sense.

    Josh

  8. G says:

    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…

  9. Trevor says:

    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.

  10. Josh Smith says:

    Trevor,

    You can do that with the VisualTreeHelper class.

    Josh

  11. Avantika says:

    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?

  12. Avantika says:

    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

  13. Pat says:

    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?

  14. Josh Smith says:

    Pat,

    You should check out the FindName method. Be sure to read about NameScopes, too.

    Josh

Follow

Get every new post delivered to your Inbox.

Join 287 other followers

%d bloggers like this: