(注:本文是《剖析WPF模板機制的內部實作》系列文章的第三篇,查看上一篇文章請點這里)
3. ItemsPanelTemplate
上一篇文章我們討論了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類注冊了這個事件:
//*******************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); }
上面代碼的意思概括就是,當一個ItemsControl的ItemsPanel屬性改變時,會觸發其ItemContainerGenerator屬性的PanelChanged事件,而一個ItemsPresenter用自己的OnPanelChanged()方法注冊了這個事件,
問題是這個ItemsPresenter是從哪里來的?又是如何與這個ItemsControl聯系在一起的?要回答這個問題我們可以查看一下UseGenerator()的參考情況,這個方法一共被呼叫過兩次,其中一次是在ItemsPresenter.AttachToOwner(),另外注意到,ItemsControl.ItemsPanel屬性的唯一一次被參考,也是在這個方法,因此這個方法一個ItemsControl和其ItemsPresenter建立連接的關鍵所在,其代碼如下:
//************ItemsPresenter**************
// 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欄位(Owner屬性)將指向這個ItemsControl,并將這個ItemsControl的ItemContainerGenerator屬性作為唯一引數傳給緊接著被呼叫的UseGenerator()方法,那么現在的關鍵是這個ItemsPresenter的TemplatedParent是從哪里來的?要回答這個問題我們需要參考一下ItemsControl的默認Template,其Xaml代碼大致如下:
<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屬性的多型性,
這里我們可以看到,ItemsPresenter的Template屬性(ItemsPanelTemplate)實際是用的其TemplateParent屬性(ItemsControl型別)的ItemsPanel屬性(ItemsPanelTemplate)的值,也就是說,我們放在ItemsControl的Template里的ItemsPresenter是沒有自己的模板的,它用的是這個ItemsControl的ItemsPanel模板,此時,這個ItemsPresenter只起到一個占位符(placeholder)的作用,在實際應用模板時,它將用這個ItemsControl的ItemsPanel模板來生成自己的visual tree,由于它自身沒有模板,因此它的visual tree完全是ItemsPanel模板實體化的結果,它的作用就是一個占位符,即指定在Template的哪個位置放置ItemsControl的ItemsPanel模板生成的visual tree,我們下一篇文章將看到ContentPresenter與之類似,也是起到一個占位符的作用,這也是我們一般很少單獨使用ItemsPresenter和ContentPresenter原因了,它們一般都會被放在Template里面,起到一個占位符的作用,
至此,ItemsPanelTemplate型別的三個重要變數:ItemsControl.ItemsPanel、ItemsPresenter.Template和GroupStyle.Panel是如何被裝配到FrameworkElement.ApplyTemplate()這個模板應用的流水線上的也就清楚了,
下一篇文章開始我們將討論DataTemplate類,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/233330.html
標籤:WPF
