UserControl/Control:如何获取模板中元素的引用
在这篇文章中,我们将探索如何通过使用 FindName 方法来访问模板子元素,即使是在 UserControl 上也可以。
当你想创建自己的自定义控件时,你有两个选择:创建一个UserControl
或继承自“Control 的类”(ContentControl
, ItemsControls
或 Control
本身)。这样做时,你肯定需要访问模板的视觉部分代码,以便为其添加良好的行为。
在这篇文章中,我们将探索如何通过使用 FindName 方法来访问模板子元素,即使是在 UserControl 上也可以。
创建控件
我不会解释如何创建自定义控件,所以这里是它的基本代码
[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)));
}
}
这是我们在“Themes\generic.xaml”文件中为其通用视觉主题定义模板的方式。请注意,我们添加了一个名为 Grid 的元素:“PART_MyGrid
”。稍后我们将从代码中查找它。
<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>
现在,我们如何从后台代码中找到网格?只需在正确的时刻访问模板:当模板被应用时。为此,我们将重写 OnApplyTemplate()
方法,并使用 FindName
方法按名称直接访问网格。然后我们可以随心所欲地操作它。
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 !");
}
顺便说一句,当你创建一个专注于可重用性的自定义控件时,你必须使用 TemplatePart
属性来声明它的不同部分
[TemplatePart(Name="PART_MyGrid",Type=typeof(Grid))]
public class CustomControl : ContentControl
{
// ....
}
创建用户控件
现在是文章中最难的部分:你创建一个用户控件作为应用程序的可重用部分。为此,你创建 C# 文件和 XAML 文件,并且由于你希望对其进行自定义,因此将其 ContentTemplate
设置如下
/// <summary>
/// Interaction logic for MyCustomUserControl.xaml
/// </summary>
public partial class MyCustomUserControl : UserControl
{
private Grid myAimedGrid;
public MyCustomUserControl()
{
InitializeComponent();
}
}
XAML 文件如下所示
<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>
然后,正如你之前看到的,你重写 OnApplyTemplate
并使用 FindName
方法获取子元素:这不会起作用!实际上,你将得到的只是“null
”或有时是 InvalidOperationException
。为什么?因为通过设置 controlTemplate
,你定义了一个 DataTemplate
,然后我们的 UserControl
使用它来应用到其内部 ContentPresenter
。因此,通过在 UserControl
上使用 findName
,我们在 UserControl
的模板中搜索名为“PART_MyGrid
”的元素,而不是在我们创建并实际使用的模板中搜索。
所以解决方案是在正确的元素上寻找元素,即 UserControl
模板的 ContentPresenter
。为此,我们将使用 VisualTreeHelper
找到它以获取 ContentPresenter
,然后使用 FindName
方法,并将其作为参数。这是代码
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 !");
}
相关链接
以下是一些关于该主题的进一步链接
结论
正如我们所看到的,没有什么是不可能的,一旦看到,实现这些不同的解决方案非常容易……祝你编码愉快!源代码解决方案已链接到文章。