2012-08-06 130 views
7

我发展我的第一个WPF自定义控件,我面临的一些问题之后调用自定义控制OnApplyTemplate,这里是我目前使用的代码的简化版本:依赖属性回调

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分配太晚了,试图把它的内容时,它仍然是零。为什么会发生这种情况,我该如何改变这种行为?

我还需要提供完整的设计时支持,但我不能找到一种方法,让我的自定义控件接受一些额外的标记,就像网格控件使用ColumnDefinitions:

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

任何帮助将是很大的不胜感激!

UPDATE

我发现了一个文件,解释为何在控制性的判定所设置的OnApplyTemplate方法被称为:

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

因此问题是:我怎么能保持跟踪属性(以XAML或编程方式)设置,以及在控件尚未初始化时调用的方法,以便在调用OnApplyTemplate方法时设置/调用它们?如何在不重复代码的情况下在控制初始化之前和之后使用相同的回调/方法?

+0

你试过打电话给base.OnApplyTemplate();在您的Template.FIndName(..)之后? – sam 2016-05-30 09:55:34

回答

5

UPDATE:

而不是你的“财产”通过查找零件推动价值变动到模板中的元素,则应该有你的模板结合到被模板上的控制性能。

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

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

如果自定义控件需要提供某些行为/功能,则实际上只需要知道“零件”并与其进行交互即可。将按钮事件挂在按钮“部分”上。

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

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

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

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

我认为这将是比较合适的“内容”使用[ContentProperty]属性,然后用你的模板ContentPresenter,以便它可以在你的按钮内注入,而不是你挂钩您的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来指定一些设计时间数据,如电网与ColumnDefinition不....清楚,只是使用属性元素语法来指定项目来填充IList/ICollection类型的属性。

因此,只需创建自己的属性,该属性可以包含您接受的类型的集合,例如

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

我描述的场景是我真正需要的简化版本:我正在包装第三方图表控件,我需要公开所有主要属性来管理它的外观,坐标轴,系列等,因此使用ContentProperty不是一个办法。感谢您澄清在自定义控件中使用特性,它对我有很大的帮助! – Kupido 2012-08-07 08:05:19

+0

老实说,使用Loaded事件似乎不是正确的方法...使用其他Microsoft或第三方控件时,为什么不需要这样做? – Kupido 2012-08-07 08:18:13

+0

使用TemplateBinding对我来说并不总是可能的,当添加轴和系列我需要访问基础图表控件时,所以模板部分必须已经可用。我部分解决了使用这里解释的技巧http://stackoverflow.com/questions/1298898/wpf-customcontrol-onapplytemplate-called-after-propertychangedcallback,但现在我遇到了绑定的问题,因为子控件没有被添加到逻辑树。我在轴/系列CollectionChanged事件处理程序中调用AddLogicalChild/RemoveLogicalChild,但这不起作用。 – Kupido 2012-08-08 09:09:30

0

我也遇到过类似的问题,OnApplyTemplateOnLoaded之前被调用。

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

+0

同样的错误也是由于缺少样式造成的。 我有一个Style =“{StaticResource MyChbStyle}”复选框,MyChbStyle没有在任何地方定义。 因此,我认为这个错误通常是由于在xaml中出现了一些错误而引起的。 – JanBrus 2017-11-06 15:37:23