首先,我介紹一下今天登場的兩位主角——Loaded事件與OnApplyTemplate方法,排名不分先後(熱烈鼓掌ING~)。
微軟是這麼解釋Loaded事件的:
Code
// Summary:
// Occurs when the element has completed layout passes, has rendered, and is
// ready for interaction.
public event RoutedEventHandler Loaded;
我想這幾句鳥語就不用我翻譯了,意思就是說這個事件是發生在這個控制項的布局已經搞定並且可以對使用者輸入做出反應的那個時間點上。
然後我再說說這個ApplyTemplate(),再看微軟的說明:Code
// Summary:
// When overridden in a derived class, is invoked whenever application code
// or internal processes (such as a rebuilding layout pass) call System.Windows.Controls.Control.ApplyTemplate().
[SecuritySafeCritical]
public virtual void OnApplyTemplate();
這幾句鳥語的意思是說,當你重載一個子類的時候,這個方法會在代碼或者內部操作(比如重新構造布局)調用ApplyTemplate()方法的時候被調用。
那麼問題來了,當一個有Template的Control在載入的時候,是先ApplyTemplate呢,還是先觸發Loaded事件呢?
答案是不一定,為此我專門寫了代碼進行測試,首先我重載了兩個通用控制項,然後重寫了它們的OnApplyTemplate()方法,代碼如下:
Code
public class MyButton:Button
{
public override void OnApplyTemplate()
{
Debug.WriteLine("Button Applied at" + DateTime.Now.ToString("o"));
base.OnApplyTemplate();
}
}
public class MyCheckBox : CheckBox
{
public override void OnApplyTemplate()
{
Debug.WriteLine("CheckBox Applied at " + DateTime.Now.ToString("o"));
base.OnApplyTemplate();
}
}
然後在Xaml中,我如此定義:
Code
<UserControl x:Class="SilverlightApplication2.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:abcd="clr-namespace:SilverlightApplication2"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300" Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<abcd:MyButton Loaded="MyButton_Loaded" HorizontalContentAlignment="Center">
<abcd:MyButton.Content>
<abcd:MyCheckBox Loaded="InsideCheckBox_Loaded"/>
</abcd:MyButton.Content>
</abcd:MyButton>
<abcd:MyCheckBox Loaded="OutsideCheckBox_Loaded"/>
</Grid>
</UserControl>
在後台代碼中:
Code
private void MyButton_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Button Loaded at " + DateTime.Now.ToString("o"));
}
private void InsideCheckBox_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Inside CheckBox Loaded at " + DateTime.Now.ToString("o"));
}
public override void OnApplyTemplate()
{
Debug.WriteLine("Page Applied at " + DateTime.Now.ToString("o"));
base.OnApplyTemplate();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Page Loaded at " + DateTime.Now.ToString("o"));
}
private void OutsideCheckBox_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Outside CheckBox Loaded at " + DateTime.Now.ToString("o"));
}
最終運行時截張VS的圖:
可能到了這大家還沒怎麼看懂這些代碼和這個圖說明了什麼,那麼我解釋一下。首先,Page類的OnApplayTemplate()方法沒有執行,是因為它沒有Template;然後,從Button、CheckBox以及Page的Loaded事件來看,CheckBox和Button作為Page的子控制項,先Load完畢,而作為Parent的Page要更晚一些Load;再然後,Button和CheckBox的Loaded事件都比ApplyTemplate早;最後,作為Button的Content的CheckBox載入最晚,而且它ApplyTemplate要比Loaded早。
這個最後一條,就是為什麼我說Loaded與ApplyTemplate的早晚不一定的原因。從上述的先後順序上,我們至少可以看出來三點:第一,子控制項總是先於父控制項觸發Loaded事件的;第二,應用了ContentPresender的控制項,它的Content如果也是一個控制項,那麼這個控制項是要比父控制項Loaded晚的;第三,第二種情況下,Content那個控制項的ApplyTemplate要比Loaded早,而不在那種情況下,控制項的Loaded事件是比ApplyTemplate早的。
有些事情微軟做得總是讓人匪夷所思,比如說ApplyTemplate居然比Loaded晚這個事情,我在WPF上測了同樣的東西,ApplyTemplate總是要比Loaded早的,這也合情合理,應用完模版之後再告訴別人我已經載入完了,這是正常人都有的邏輯,但是在Silverlight中,大部分情況都是反過來的。我當然不相信設計Silverlight那哥們在寫架構的時候把調用順序給弄倒了,微軟這麼做總是有它的理由的,只是理由是什麼呢?是不是案頭上和瀏覽器上繪圖會有區別呢?這些我就不得而知了,沒準只有微軟寫OS和IE核心的人才知道……
最後,我說說Silverlight這種機制的問題,就是比如我寫一個控制項,然後在generic.xaml中為這個控制項寫了Template,我需要通過重寫OnApplyTemplate方法調用GetTemplateChild來獲得Template中的子控制項的的執行個體,由於這個過程比Loaded事件要晚,因此如果我在Loaded事件觸發時就為控制項範本中的子控制項的某些屬性賦值的話,由於子控制項還沒有初始化,所以這樣會引發NullReferenceException。解決方案並不是沒有,只需要我們手動設用ApplyTemplate方法即可,但是這樣破壞了類的預設行為,我認為並不是很好。
對於上述觀點,由於測試得比較倉促,也許會有一些我沒有發現在問題在裡面,也歡迎大家對我進行指正。