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

  • A+
所属分类:.NET技术
摘要

(注:本文是《剖析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类。