自定义控件OnApplyTemplate在依赖项属性回调之后调用 [英] Custom control OnApplyTemplate called after dependency property callback

查看:269
本文介绍了自定义控件OnApplyTemplate在依赖项属性回调之后调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发我的第一个WPF自定义控件,并且遇到一些问题,这是我当前使用的代码的简化版本:

I'm developing my first WPF custom control and I'm facing some problems, here's a simplified version of the code I'm currently using:

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;
        }
    }
}

这是我的模板自定义控件:

This is the template for my custom control:

<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属性:

Then i put it inside a Grid and try to set its Content property:

<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。为什么会发生这种情况,以及如何更改此行为?

This doesn't work, for some reason the OnContentPropertyChanged callback is called before OnApplyTemplate, so myControl._buttonElement is assigned too late and it's still null when trying to set its content. Why is this happening and how can I change this behavior?

我还需要提供完整的设计时间支持,但是我找不到让我的自定义控件接受其他一些方法的方法标记,就像Grid控件使用ColumnDefinitions一样:

I also need to provide full design time support but I cannot find a way to make my custom control accept some additional markup, much like the Grid control does with ColumnDefinitions:

<Grid x:Name="layoutRoot">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
</Grid>

任何帮助将不胜感激!

更新

我找到了一个文档,该文档解释了为什么在设置控件属性后调用OnApplyTemplate方法:

I found a document that explains why the OnApplyTemplate method is called after control properies are set:

http://msdn.microsoft.com /en-us/library/dd351483%28v=vs.95%29.aspx

所以问题是:如何跟踪属性(在XAML中或以编程方式)设置的以及在未初始化控件时调用的方法,以便在调用OnApplyTemplate方法时可以设置/调用它们?

So the question is: how can I keep track of the properties that are set (in XAML or programmatically) and methods that are called when the control has not been initialized, so that I can set/call them when the OnApplyTemplate method is called? How can the same callback/method work both before and after the control initialization without duplicating the code?

推荐答案

UPDATE:

与其通过部件将价值更改推入到模板中的元素中,还不如将您的模板绑定到要进行模板化的控件的属性上。

Instead of your "property" pushing changes in value into elements in your template by looking for Parts, you should instead have your template bind to properties on the control being templated.

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

Normally this is done using presenters within a template e.g. ContentPresenter binds to the property designated as the "content" (it finds out that name by looking for the [ContentProperty] attribute), and by using bindings in your template that use TemplateBinding or TemplatedParent to connect to the properties on your custom control.

那么,设置属性的顺序和应用模板的时间就没有问题了。因为它是为模板提供外观的模板在控件上设置的数据/属性。

Then there is no issue about what order you set your properties and when the template is applied....because it is the template that provides the "look" for the data/properties set on your control.

自定义控件仅在需要提供某些行为/功能时才真正需要知道部分并与之交互。将点击事件挂在按钮部件上。

A custom control should only really need to know and interact with "parts" if it needs to provide certain behaviour/functionality e.g. hooking the click event on a button "part".

在这种情况下,您应该将模板绑定到财产。我在下面提供的示例说明了Content属性通常是如何完成的。

In this case instead of setting the Content in the constructor in code-behind, you should get your template to bind to the property. The example I gave below showed how that was generally done with a Content property.

或者,您可以更明确地提取属性,例如

Alternatively you could pull out properties more explicitly e.g. this could be inside your template.

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

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






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


I think it would be better to designate your "content" using [ContentProperty] attribute, and then using a ContentPresenter in your template so that it can be injected inside your Button, rather than you hooking your Content DependencyProperty. (if you inherit from ContentControl then that provides the "content" behaviour).

[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一样。 ...那么这只是使用Property Element语法来指定要填充IList / ICollection类型属性的项目。

As for you wanting to be able to specify some design time data via XAML like Grid does with ColumnDefinition....well that is just using Property Element syntax to specify the items to fill an IList/ICollection typed property.

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

So just create your own property that can hold a collection of the type you accept e.g.

public List<MyItem> MyItems { get; set; }    // create in your constructor.

这篇关于自定义控件OnApplyTemplate在依赖项属性回调之后调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆