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

  • 相关阅读:
    java序列化和反序列化使用总结
    什么是N+1查询?
    Oracle insert /*+ APPEND */原理解析
    Oracle redo与undo
    MongoDB(三)-- 执行JS、界面工具
    几种Bean的复制方法性能比较
    Kafka(三)-- Kafka主要参数
    Kafka(二)-- 安装配置
    Kafka(一)-- 初体验
    Nginx(十二)-- Nginx+keepalived实现高可用
  • 原文地址:https://www.cnblogs.com/firebet/p/14518197.html
Copyright © 2020-2023  润新知