自定义控件OnApplyTemplate在依赖项属性回调后调用

时间:2012-08-06 12:07:07

标签: wpf custom-controls

我正在开发我的第一个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>

然后我把它放在一个Grid中并尝试设置它的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方法中设置/调用它们叫做?如何在控制初始化之前和之后同时使用相同的回调/方法而不重复代码?

2 个答案:

答案 0 :(得分:5)

更新:

而不是通过查找部件将“值”更改为模板中元素的“属性”,而应将模板绑定到模板控件上的属性。

通常,这是使用模板中的演示者完成的,例如ContentPresenter绑定到指定为“内容”的属性(通过查找[ContentProperty]属性找到该名称),并使用模板中使用TemplateBinding或{{的绑定1}}连接到自定义控件上的属性。

然后,关于您设置属性的顺序以及应用模板的时间没有问题....因为它是为控件上设置的数据/属性提供“外观”的模板。

如果需要提供某些行为/功能,则自定义控件应该只需要知道“部件”并与之交互。将点击事件挂钩在“部分”按钮上。

在这种情况下,您应该将模板绑定到属性,而不是在代码隐藏中的构造函数中设置Content。我在下面给出的示例显示了通常如何使用Content属性。

或者,您可以更明确地提取属性,例如这可能在你的模板中。

TemplatedParent

我认为最好使用[ContentProperty]属性指定“内容”,然后在模板中使用ContentPresenter,以便可以将其注入Button中,而不是挂钩Content DependencyProperty。 (如果从ContentControl继承,则提供“内容”行为)。

 <Label Content="{TemplateBinding MyPropertyOnMyControl}" .....

 <Button Content="{TemplateBinding AnotherPropertyOnMyControl}" .....

[TemplatePart(Name = "PART_Button", Type = typeof (Button))]
public class MyControl : Control
[ContentProperty("Content")]

至于你希望能够通过XAML指定一些设计时数据,就像Grid使用ColumnDefinition一样......那就是使用Property Element语法来指定填充IList / ICollection类型属性的项。

所以,只需创建自己的属性即可保存您接受的类型的集合,例如

<ControlTemplate TargetType="{x:Type local:MyControl}">
<Button x:Name="PART_Button">
<ContentPresenter/>
</Button>
</ControlTemplate>

答案 1 :(得分:0)

我遇到了类似的问题,OnApplyTemplate之前调用了OnLoaded

问题是由于xaml中的绑定。我在其中一个复选框上将布尔属性绑定到Checked而不是IsChecked