2012-08-06 83 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