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类。

相关文章
相关标签/搜索