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:
[TemplatePart(Name = "PART_MyGrid", Type = typeof(Grid))]
public class MyCustomControl : ContentControl
{
private Grid myAimedGrid;
static MyCustomControl()
{
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.
<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.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
myAimedGrid = base.Template.FindName("PART_MyGrid", this) as Grid;
myAimedGrid.PreviewMouseDown +=
new MouseButtonEventHandler(myAimedGrid_PreviewMouseDown);
}
void myAimedGrid_PreviewMouseDown(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
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:
[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:
public partial class MyCustomUserControl : UserControl
{
private Grid myAimedGrid;
public MyCustomUserControl()
{
InitializeComponent();
}
}
The XAML file is as shown below:
<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:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ContentPresenter presenter = (ContentPresenter)
(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this, 0), 0));
presenter.ApplyTemplate();
myAimedGrid =
presenter.ContentTemplate.FindName("PART_MyGrid", presenter) as Grid;
myAimedGrid.PreviewMouseDown
+= new MouseButtonEventHandler(myAimedGrid_PreviewMouseDown);
}
void myAimedGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
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.