WPF源代碼分析系列一:剖析WPF模板機制的內部實現(三)

(注:本文是《剖析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,並覆寫了TemplateInternalTemplateCache屬性。如下是相關代碼:

//************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類。

相關文章
相關標籤/搜索