WPF源代码分析系列一:剖析WPF模板机制的内部实现(三)

(注:本文是《剖析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,并覆写了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属性的多态性。

这里我们可以看到,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类。

 

posted @ 2020-12-10 20:20  至简之道  阅读(753)  评论(0编辑  收藏  举报