wpf - 依赖属性回调后调用的自定义控件 OnApplyTemplate

标签 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";
        }
    }
}

这不起作用,由于某种原因,在 OnApplyTemplate 之前调用了 OnContentPropertyChanged 回调,因此 myControl._buttonElement 分配得太晚,并且在尝试设置其内容时它仍然为空。为什么会发生这种情况,我该如何改变这种行为?

我还需要提供完整的设计时支持,但我找不到让我的自定义控件接受一些额外标记的方法,就像 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 的绑定(bind)来绑定(bind)或 TemplatedParent连接到自定义控件上的属性。

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

如果自定义控件需要提供某些行为/功能,例如将点击事件卡在按钮“部分”上。

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

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

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

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

我认为使用 [ContentProperty] 属性指定您的“内容”会更好,然后在模板中使用 ContentPresenter 以便可以将其注入(inject)到您的 Button 中,而不是 Hook 您的 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 所做的那样......嗯,这只是使用 Property Element 语法来指定填充 IList/ICollection 类型属性的项目。

因此,只需创建您自己的属性,该属性可以包含您接受的类型的集合,例如
public List<MyItem> MyItems { get; set; }    // create in your constructor.

关于wpf - 依赖属性回调后调用的自定义控件 OnApplyTemplate,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11827962/

相关文章:

wpf - 将实现注入(inject)可移植类库的模式是否有名称?

wpf - F# & WPF : basic UI update

mvvm - UWP 自定义 ListView 向下滚动

javascript - html5视频: play/pause custom button issue

java - Apache Wicket - 将 HTML 添加到反馈面板

c# - 在 VS2010 中向后移植 .NET 4 到 3.5 SP1

c# - 如何在 .NET Core 3.0 中为 WPF 应用程序引用 System.Windows.Forms?

wpf - 将 ListView 自动滚动到最后添加的项目的最佳方式是什么?

winforms - 使用自定义 OnPaint 在 F# 中自定义 Windows.Forms 控件?

android - 在带有边框的 textView 中创建自定义 UI 组件按钮