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 to 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()
{
//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.
<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()
{
//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:[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 itsContentTemplate
as below: /// <summary>
/// Interaction logic for MyCustomUserControl.xaml
/// </summary>
public partial class MyCustomUserControl : UserControl
{
private Grid myAimedGrid;
public MyCustomUserControl()
{
InitializeComponent();
}
}
The XAML file:
<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();
//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 !");
}
0 comments:
Post a Comment