Click here to Skip to main content
15,886,019 members
Articles / Desktop Programming / WPF

UserControl/Control : How to Get a Reference to an Element of the Template

Rate me:
Please Sign up or sign in to vote.
4.75/5 (4 votes)
20 Sep 2010Ms-PL2 min read 20.2K   10  
In this post, we'll discover how to access the template children by using the FindName method even on UserControl.

When you want to create your own custom control, you have two choices: create a UserControl or inherit from one of the "Control's classes" (ContentControl, ItemsControls or Control itself). When doing so, you'll surely need to access 5 the visual parts of your template from the code to add to it a nice behavior.

In this post, we'll discover how to access the template children by using the FindName method even on UserControl.

You Create a Control

I won't explain how to create a custom control, so here is its base code:

C#
[TemplatePart(Name = "PART_MyGrid", Type = typeof(Grid))]
  public class MyCustomControl : ContentControl
  {
    private Grid myAimedGrid;
 
    static MyCustomControl()
    {
	  //Overrides the style by ours
      DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl),
          new FrameworkPropertyMetadata(typeof(MyCustomControl)));
    }
  }

And here is how we define its template for the generic visual theme in the "Themes\generic.xaml" file. Notice that we add a named Grid :"PART_MyGrid". We'll seek for it later from the code.

XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:FindNamesApplication.MyContentControl">
  <Style TargetType="{x:Type local:MyCustomControl}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:MyCustomControl }">
          <Grid x:Name="PART_MyGrid" Background="Black" Width="{TemplateBinding Width}"
              Height="{TemplateBinding Height}">
            <ContentPresenter Content="{TemplateBinding Content}" />
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary> 

Now, how can we find the grid from the code behind? Simply by accessing the template at the right moment: when the template is applied. To do so, we will override the OnApplyTemplate() method and access directly to the grid by its name with the FindName method. We can then act on it as we wish.

C#
public override void OnApplyTemplate()
{
  //Effectively apply the template
  base.OnApplyTemplate();
 
  //Find the grid in the template once it's applied
  myAimedGrid = base.Template.FindName("PART_MyGrid", this) as Grid;
 
  //We can subscribe to its events
  myAimedGrid.PreviewMouseDown += 
     new MouseButtonEventHandler(myAimedGrid_PreviewMouseDown);
}
 
void myAimedGrid_PreviewMouseDown(object sender,
 System.Windows.Input.MouseButtonEventArgs e)
{
  //Proof 
  MessageBox.Show("Mouse preview Down on the grid !");
} 

By the way, when you create a custom control which is focused on reusability, you should imperatively declare its different parts by using the TemplatePart attribute:

C#
[TemplatePart(Name="PART_MyGrid",Type=typeof(Grid))]
public class CustomControl : ContentControl
{
// ....
} 

You Create a User Control

Now the hardest part of the post: you create a user control as a reusable part of your application. To do so, you create the C# file and the XAML file and as you want it to be customized, you set its ContentTemplate as below:

C#
/// <summary>
/// Interaction logic for MyCustomUserControl.xaml
/// </summary>
public partial class MyCustomUserControl : UserControl
{
  private Grid myAimedGrid;
 
  public MyCustomUserControl()
  {
    InitializeComponent();
  }
} 

The XAML file is as shown below:

XML
<UserControl x:Class="FindNamesApplication.MyUserControl.MyCustomUserControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        mc:Ignorable="d" d:DesignHeight="300"
        d:DesignWidth="300">
    <UserControl.ContentTemplate>
        <DataTemplate>
            <Grid x:Name="PART_MyGrid" Background="Black" 
            Width="{TemplateBinding Width}"
                    Height="{TemplateBinding Height}">
                <ContentPresenter Content="{TemplateBinding Content}" />
            </Grid>
        </DataTemplate>
    </UserControl.ContentTemplate>
</UserControl> 

Then, as you have seen before, you override the OnApplyTemplate and get the child with the FindName methods: this won't do the job ! Actually, all you will get is 'null' or an InvalidOperationException sometimes. Why? Because by setting the controlTemplate, you define a DataTemplate which is then used by our UserControl to be applied on its internal ContentPresenter. So by using findName on the UserControl, we search the element named "PART_MyGrid" in the template of the UserControl and not in the template created by us and actually used.

So the solution is to seek the element on the correct element which is the ContentPresenter of the template of the UserControl. To do so, we'll find it using the VisualTreeHelper to get the ContentPresenter and then use the FindName method with it as a parameter. Here is the code:

C#
public override void OnApplyTemplate()
{
  base.OnApplyTemplate();
 
  //The ContentPresenter is the second child of the UserControl...
  ContentPresenter presenter = (ContentPresenter)
    (VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this, 0), 0));
 
  //Be sure that the template is applied on the presenter
  presenter.ApplyTemplate();
 
  //get the grid from the presenter
  myAimedGrid =
    presenter.ContentTemplate.FindName("PART_MyGrid", presenter) as Grid;
 
  //We can subscribe to its events
  myAimedGrid.PreviewMouseDown
    += new MouseButtonEventHandler(myAimedGrid_PreviewMouseDown);
}
 
void myAimedGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
  //Proof 
  MessageBox.Show("Mouse preview Down on the grid !");
} 

Interesting Links

Here are some links to go further on the subject:

Conclusion

As we can see, nothing is impossible and once seen, it's quite easy to implement these different solutions... Have a great code! The source solution is linked to the post.

This article was originally posted at http://blog.lexique-du-net.com/index.php

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer http://wpf-france.fr
France (Metropolitan) France (Metropolitan)
Jonathan creates software, mostly with C#,WPF and XAML.

He really likes to works on every Natural User Interfaces(NUI : multitouch, touchless, etc...) issues.



He is awarded Microsoft MVP in the "Client Application Development" section since 2011.


You can check out his WPF/C#/NUI/3D blog http://www.jonathanantoine.com.

He is also the creator of the WPF French community web site : http://wpf-france.fr.

Here is some videos of the projects he has already work on :

Comments and Discussions

 
-- There are no messages in this forum --