(注:本文是《剖析WPF模板機制的內部實現》系列文章的第三篇,查看上一篇文章點這裏)html
3. ItemsPanelTemplateide
上一篇文章咱們討論了ControlTemplate模板類,在這一篇咱們將討論ItemsPanelTemplate類。函數
ItemsPanelTemplate類型的變量主要有:ItemsControl.ItemsPanel,ItemsPresenter.Template,GroupStyle.Panel,DataGridRow.ItemsPanel等。這裏重點討論前二者,同時順帶提一下第三者。首先,ItemsControl.ItemsPanel屬性定義以下:工具
//***************ItemsControl***************** public static readonly DependencyProperty ItemsPanelProperty = DependencyProperty.Register("ItemsPanel", typeof(ItemsPanelTemplate), typeof(ItemsControl), new FrameworkPropertyMetadata(GetDefaultItemsPanelTemplate(), OnItemsPanelChanged)); private static ItemsPanelTemplate GetDefaultItemsPanelTemplate() { ItemsPanelTemplate template = new ItemsPanelTemplate(new FrameworkElementFactory(typeof(StackPanel))); template.Seal(); return template; } /// <summary> /// ItemsPanel is the panel that controls the layout of items. /// (More precisely, the panel that controls layout is created /// from the template given by ItemsPanel.) /// </summary> public ItemsPanelTemplate ItemsPanel { get { return (ItemsPanelTemplate) GetValue(ItemsPanelProperty); } set { SetValue(ItemsPanelProperty, value); } } private static void OnItemsPanelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((ItemsControl) d).OnItemsPanelChanged((ItemsPanelTemplate) e.OldValue, (ItemsPanelTemplate) e.NewValue); } protected virtual void OnItemsPanelChanged(ItemsPanelTemplate oldItemsPanel, ItemsPanelTemplate newItemsPanel) { ItemContainerGenerator.OnPanelChanged(); }
從依賴屬性ItemsPanelProperty的註冊參數可知ItemsControl.ItemsPanel默認用的是一個StackPanel控件。其回調函數調用了ItemContainerGenerator.OnPanelChanged(),這個方法只有一個可執行語句:佈局
//**************ItemContainerGenerator****************
internal void OnPanelChanged() { if (PanelChanged != null) PanelChanged(this, EventArgs.Empty); }
這個語句檢查一個ItemContainerGenerator的PanelChanged事件是否被註冊,若是有註冊則調用事件處理函數。用代碼工具查看,只有ItemsPresenter類類註冊了這個事件:this
//*******************ItemsPresenter********************** void UseGenerator(ItemContainerGenerator generator) { if (generator == _generator) return; if (_generator != null) _generator.PanelChanged -= new EventHandler(OnPanelChanged); _generator = generator; if (_generator != null) _generator.PanelChanged += new EventHandler(OnPanelChanged); } private void OnPanelChanged(object sender, EventArgs e) { // something has changed that affects the ItemsPresenter. // Re-measure. This will recalculate everything from scratch. InvalidateMeasure(); // // If we're under a ScrollViewer then its ScrollContentPresenter needs to // be updated to work with the new panel. // ScrollViewer parent = Parent as ScrollViewer; if (parent != null) { // If our logical parent is a ScrollViewer then the visual parent is a ScrollContentPresenter. ScrollContentPresenter scp = VisualTreeHelper.GetParent(this) as ScrollContentPresenter; if (scp != null) { scp.HookupScrollingComponents(); } } }
這些代碼的意思簡而言之就是,當一個ItemsControl的ItemsPanel屬性改變時,會觸發其ItemContainerGenerator屬性的PanelChanged事件,而一個ItemsPresenter註冊了用本身的OnPanelChanged()方法註冊了這個事件。這個方法的一個工做是調用InvalidateMeasure()方法,將這個ItemsPresenter的measurement狀態標記爲失效(Invalidated),從而進入隊列等待下一次佈局更新時從新measure,而咱們前面提到過,FrameworkElement及其子類控件每一次measure時都會調用FrameworkElement.ApplyTemplate()方法。spa
問題是這個ItemsPresenter是從哪裏來的?是如何與ItemsControl聯繫在一塊兒的?要回答這個問題就必須回到上面兩個方法的第一個方法UseGenerator()。這個方法一共被調用過兩次,其中一次是在ItemsPresenter.AttachToOwner()。code
另外,ItemsControl.ItemsPanel屬性也只有一處引用,也是在這個方法。事實上,這個方法是一個ItemsControl和其ItemsPresenter創建鏈接的關鍵地方。其代碼以下:orm
//************ItemsPresenter.cs**************
// initialize (called during measure, from ApplyTemplate) void AttachToOwner() { DependencyObject templatedParent = this.TemplatedParent; ItemsControl owner = templatedParent as ItemsControl; ItemContainerGenerator generator; if (owner != null) { // top-level presenter - get information from ItemsControl generator = owner.ItemContainerGenerator; } else { // subgroup presenter - get information from GroupItem GroupItem parentGI = templatedParent as GroupItem; ItemsPresenter parentIP = FromGroupItem(parentGI); if (parentIP != null) owner = parentIP.Owner; generator = (parentGI != null) ? parentGI.Generator : null; } _owner = owner; UseGenerator(generator); // create the panel, based either on ItemsControl.ItemsPanel or GroupStyle.Panel ItemsPanelTemplate template = null; GroupStyle groupStyle = (_generator != null) ? _generator.GroupStyle : null; if (groupStyle != null) { // If GroupStyle.Panel is set then we dont honor ItemsControl.IsVirtualizing template = groupStyle.Panel; if (template == null) { // create default Panels if (VirtualizingPanel.GetIsVirtualizingWhenGrouping(owner)) { template = GroupStyle.DefaultVirtualizingStackPanel; } else { template = GroupStyle.DefaultStackPanel; } } } else { // Its a leaf-level ItemsPresenter, therefore pick ItemsControl.ItemsPanel template = (_owner != null) ? _owner.ItemsPanel : null; } Template = template; }
能夠看到若是一個ItemsPresenter的TemplatedParent可以轉換爲一個ItemsControl,則其_owner字段將指向這個ItemsControl,並且方法
UseGenerator()的入參generator就是這個ItemsControl的ItemContainerGenerator。那麼一個ItemsPresenter的TemplatedParent是從哪裏來的?要回答這個問題咱們須要參考一下ItemsControl的默認Template,其Xaml代碼大體以下:htm
<Style x:Key="ItemsControlStyle1" TargetType="{x:Type ItemsControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ItemsControl}"> <Border> <ItemsPresenter/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
原來,ItemsControl根據Template模板生成本身的visual tree,在實例化ItemsPresenter時會刷新其TemplatedParent屬性,將其指向本身。這個過程比較底層,咱們只須要知道流程大體是這樣就能夠了。
此外,從註釋也能夠看出這個方法很是重要,FrameworkElement.ApplyTemplate()將用到它。事實上ItemsPresnter類覆寫了FrameworkElement.OnPreApplyTemplate()方法,並在這裏調用了這個方法:
//************ItemsPresenter************** /// <summary>
/// Called when the Template's tree is about to be generated
/// </summary> internal override void OnPreApplyTemplate() { base.OnPreApplyTemplate(); AttachToOwner(); }
ItemsPresenter.AttachToOwner()方法的另外一個重要工做是根據字段_generator的GroupStyle屬性是否爲空,來爲Template屬性選擇模板。其中最關鍵的是倒數第二個語句:
template = (_owner != null) ? _owner.ItemsPanel : null;
這意味着,若是一個ItemsPresenter的TemplateParent是一個ItemsControl,並且不是用的groupStyle,這個ItemsPresenter的Template將被指向這個ItemsControl的ItemsPanel。這樣ItemsControl.ItemsPanel就和ItemsPresenter.Template聯繫在了一塊兒。
那麼這個Template的做用是什麼呢?事實上,ItemsPresenter繼承自FrameworkElement,並覆寫了TemplateInternal和TemplateCache屬性。如下是相關代碼:
//************ItemsPresenter************** // Internal Helper so the FrameworkElement could see this property internal override FrameworkTemplate TemplateInternal { get { return Template; } } // Internal Helper so the FrameworkElement could see the template cache internal override FrameworkTemplate TemplateCache { get { return _templateCache; } set { _templateCache = (ItemsPanelTemplate)value; } } internal static readonly DependencyProperty TemplateProperty = DependencyProperty.Register( "Template", typeof(ItemsPanelTemplate), typeof(ItemsPresenter), new FrameworkPropertyMetadata( (ItemsPanelTemplate) null, // default value FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnTemplateChanged))); private ItemsPanelTemplate Template { get { return _templateCache; } set { SetValue(TemplateProperty, value); } } // Internal helper so FrameworkElement could see call the template changed virtual internal override void OnTemplateChangedInternal(FrameworkTemplate oldTemplate, FrameworkTemplate newTemplate) { OnTemplateChanged((ItemsPanelTemplate)oldTemplate, (ItemsPanelTemplate)newTemplate); } private static void OnTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ItemsPresenter ip = (ItemsPresenter) d; StyleHelper.UpdateTemplateCache(ip, (FrameworkTemplate) e.OldValue, (FrameworkTemplate) e.NewValue, TemplateProperty); }
是否似曾相識?這些代碼和Control類幾乎徹底同樣,除了Template屬性的類型從ControlTemplate變成了ItemsPanelTemplate。正如前面提到的,這是FrameworkElement的子類對FrameworkElement.TemplateInternal屬性實現多態性的一種經常使用模式。這種模式的主要目的是提供一個經過修改Template屬性來改變FrameworkElement.TemplateInternal屬性值的機制。
因爲流程比較複雜,咱們這裏再梳理一下:一個ItemsControl應用模板是,會實例化Template的ItemsPresenter,並將其_templateParent字段指向這個ItemsControl. 而在ApplyTemplate時,ItemsPresenter覆寫了FrameworkElement.OnPreApplyTemplate()以調用AttachToOwner(),將_templateParent.ItemsPanel屬性(或GroupStyle.Panel,若是設定了GroupStyle)的值賦給Template,從而實現TemplateInternal屬性的多態性。
至此,ItemsPanelTemplate類型的三個重要變量:ItemsControl.ItemsPanel、ItemsPresenter.Template和GroupStyle.Panel是如何被裝配到FrameworkElement.ApplyTemplate()這個模板應用的流水線上的也就清楚了。
下一篇文章開始咱們將討論DataTemplate類。