我正在开发我的第一个WPF自定义控件,并且遇到了一些问题,这是我当前使用的代码的简化版本:
using System.Windows;
using System.Windows.Controls;
namespace MyControls
{
[TemplatePart(Name = "PART_Button", Type = typeof (Button))]
public class MyControl : Control
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof (object), typeof (MyControl), new PropertyMetadata(null, OnLabelPropertyChanged));
private Button _buttonElement;
public object Content
{
get { return this.GetValue(LabelProperty); }
set { this.SetValue(ContentProperty, value); }
}
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof (MyControl), new FrameworkPropertyMetadata(typeof (MyControl)));
}
private static void OnContentPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyControl myControl = sender as MyControl;
if (myControl != null && myControl._buttonElement != null)
myControl._buttonElement.Content = e.NewValue;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this._buttonElement = this.Template.FindName("PART_Button", this) as Button;
}
}
}
这是我的自定义控件的模板:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControls">
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyControl}">
<Button x:Name="PART_Button" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
然后,我将其放入网格中并尝试设置其Content属性:
<Grid x:Name="layoutRoot">
<controls:MyControl x:Name="myControl" />
</Grid>
这是背后的代码:
using System.Windows;
namespace MyControls
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.myControl.Content = "test";
}
}
}
这是行不通的,由于某种原因,OnContentPropertyChanged回调在OnApplyTemplate之前被调用,因此myControl._buttonElement分配得太晚了,并且在尝试设置其内容时仍然为null。为什么会发生这种情况,我该如何改变这种行为?
我还需要提供完整的设计时支持,但是我找不到一种使我的自定义控件接受一些附加标记的方法,就像Grid控件使用ColumnDefinitions一样:
<Grid x:Name="layoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
任何帮助将不胜感激!
更新
我找到了一个文档,该文档解释了为什么在设置控件属性后调用OnApplyTemplate方法:
http://msdn.microsoft.com/en-us/library/dd351483%28v=vs.95%29.aspx
因此,问题是:如何跟踪未初始化控件时设置的属性(在XAML中或以编程方式)和被调用的方法,以便在调用OnApplyTemplate方法时可以设置/调用它们?在控件初始化之前和之后如何在不复制代码的情况下,如何使用相同的回调/方法?
最佳答案
更新:
代替您的“属性”通过查找“零件”将值的更改推送到模板中的元素中,您应该将模板绑定(bind)到要模板化的控件上的属性。
通常,这是使用模板中的演示者来完成的,例如ContentPresenter
绑定(bind)到指定为“内容”的属性(它通过查找[ContentProperty]
属性来找到该名称),并在模板中使用TemplateBinding
或TemplatedParent
绑定(bind)到自定义控件上的属性。
这样就不会对您设置属性的顺序以及何时应用模板产生任何问题……因为它是为控件上设置的数据/属性提供“外观”的模板。
自定义控件仅在需要提供某些行为/功能时才真正需要知道“部件”并与之交互。将点击事件卡在按钮“部件”上。
在这种情况下,您应该让模板绑定(bind)到该属性,而不是在代码后面的构造函数中设置Content。我在下面给出的示例说明了Content属性通常是如何完成的。
另外,您可以更明确地提取属性,例如这可能在您的模板中。
<Label Content="{TemplateBinding MyPropertyOnMyControl}" .....
<Button Content="{TemplateBinding AnotherPropertyOnMyControl}" .....
我认为最好使用[ContentProperty]属性指定“内容”,然后在模板中使用ContentPresenter,以便可以将其注入(inject)Button中,而不是钩住Content DependencyProperty。 (如果您从ContentControl继承,则将提供“内容”行为)。
[TemplatePart(Name = "PART_Button", Type = typeof (Button))]
public class MyControl : Control
[ContentProperty("Content")]
和
<ControlTemplate TargetType="{x:Type local:MyControl}">
<Button x:Name="PART_Button">
<ContentPresenter/>
</Button>
</ControlTemplate>
至于您希望能够通过XAML指定一些设计时数据,例如Grid对ColumnDefinition .... well所做的只是使用Property Element语法来指定用于填充IList/ICollection类型属性的项目。
因此,只需创建自己的属性即可容纳您接受的类型的集合,例如
public List<MyItem> MyItems { get; set; } // create in your constructor.
关于wpf - 自定义控件OnApplyTemplate在依赖项属性回调之后调用,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/11827962/