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

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

(注:本文是《剖析WPF模板机制的内部实现》系列文章的最后一篇文章,查看上一篇文章请点这里)

(注:本文是《剖析WPF模板机制的内部实现》系列文章的最后一篇文章,查看上一篇文章请点这里)

上一篇文章我们讨论了DataTemplate类型的两个重要变量,ContentControl.ContentTemplate和ContentPresenter.ContentTemplate,这一篇将讨论这个类型的另一个重要变量ItemsControl.ItemTemplate

4.2ItemsControl.ItemTemplate

我们都知道ItemsControl控件在WPF中的重要性,ItemsControl.ItemTemplate用的也非常多,那么其在模板应用中的角色是什么呢?要回答这个问题,我们先看其定义:

    public static readonly DependencyProperty ItemTemplateProperty =                 DependencyProperty.Register(                         "ItemTemplate",                         typeof(DataTemplate),                         typeof(ItemsControl),                         new FrameworkPropertyMetadata(                                 (DataTemplate) null,                                 OnItemTemplateChanged));          /// <summary>         ///     ItemTemplate is the template used to display each item.         /// </summary>public DataTemplate ItemTemplate         {             get { return (DataTemplate) GetValue(ItemTemplateProperty); }             set { SetValue(ItemTemplateProperty, value); }         }          private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)         {             ((ItemsControl) d).OnItemTemplateChanged((DataTemplate) e.OldValue, (DataTemplate) e.NewValue);         }          protected virtual void OnItemTemplateChanged(DataTemplate oldItemTemplate, DataTemplate newItemTemplate)         {             CheckTemplateSource();               if (_itemContainerGenerator != null)             {                 _itemContainerGenerator.Refresh();             }         }

 可以看到当ItemsControl.ItemTemplate改变时,会调用_itemContainerGenerator.Refresh()。这个方法的定义如下:

        // regenerate everything         internal void Refresh()         {             OnRefresh();         }          // Called when the items collection is refreshed         void OnRefresh()         {             ((IItemContainerGenerator)this).RemoveAll();              // tell layout what happened             if (ItemsChanged != null)             {                 GeneratorPosition position = new GeneratorPosition(0, 0);                 ItemsChanged(this, new ItemsChangedEventArgs(NotifyCollectionChangedAction.Reset, position, 0, 0));             }         }

可见这个方法调用OnRefresh(),后者的主要工作清空已经生成的元素,并触发ItemsChanged事件,告诉所有监听者列表已经被重置。

查找ItemsControl.ItemTemplate的引用会发现一个值得注意的方法ItemsControl.PrepareContainerForItemOverride

//*********************ItemsControl*************************

/// <summary> /// Prepare the element to display the item. This may involve /// applying styles, setting bindings, etc. /// </summary> protected virtual void PrepareContainerForItemOverride(DependencyObject element, object item) { // Each type of "ItemContainer" element may require its own initialization. // We use explicit polymorphism via internal methods for this. // // Another way would be to define an interface IGeneratedItemContainer with // corresponding virtual "core" methods. Base classes (ContentControl, // ItemsControl, ContentPresenter) would implement the interface // and forward the work to subclasses via the "core" methods. // // While this is better from an OO point of view, and extends to // 3rd-party elements used as containers, it exposes more public API. // Management considers this undesirable, hence the following rather // inelegant code. HeaderedContentControl hcc; ContentControl cc; ContentPresenter cp; ItemsControl ic; HeaderedItemsControl hic; if ((hcc = element as HeaderedContentControl) != null) { hcc.PrepareHeaderedContentControl(item, ItemTemplate, ItemTemplateSelector, ItemStringFormat); } else if ((cc = element as ContentControl) != null) { cc.PrepareContentControl(item, ItemTemplate, ItemTemplateSelector, ItemStringFormat); } else if ((cp = element as ContentPresenter) != null) { cp.PrepareContentPresenter(item, ItemTemplate, ItemTemplateSelector, ItemStringFormat); } else if ((hic = element as HeaderedItemsControl) != null) { hic.PrepareHeaderedItemsControl(item, this); } else if ((ic = element as ItemsControl) != null) { if (ic != this) { ic.PrepareItemsControl(item, this); } } }

这个方法的主要工作是根据第一个入参element的类型,做一些准备工作。如HeaderedContentControl和HeaderedItemsControl会把ItemTemplate的值赋给HeaderTemplate,而ContentControl和ContentPresenter则会用它更新ContentTemplate。如果是element也是ItemsControl,这意味着一个ItemsControl的ItemTemplate里又嵌套了一个ItemsControl,这时就把父控件的ItemTemplate传递给子控件的ItemTemplate。

那么问题是ItemsControl.PrepareContainerForItemOverride()方法什么时候会被调用呢?查看引用发现,这个方法会被ItemsControl.PrepareItemContainer()调用,后者又会被ItemContainerGenerator.PrepareItemContainer()方法调用。

继续追踪发现,Panel类通过Generator属性调用了ItemContainerGenerator.PrepareItemContainer()方法。Panel.Generator属性是一个ItemContainerGenerator类型的只读属性,其支撑字段是_itemContainerGenerator。那么Panel的Generator属性又是从哪里得到的呢?秘密就在下面这个方法:

//*************Panel*************

private void ConnectToGenerator() { ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this); if (itemsOwner == null) { // This can happen if IsItemsHost=true, but the panel is not nested in an ItemsControl throw new InvalidOperationException(SR.Get(SRID.Panel_ItemsControlNotFound)); } IItemContainerGenerator itemsOwnerGenerator = itemsOwner.ItemContainerGenerator; if (itemsOwnerGenerator != null) { _itemContainerGenerator = itemsOwnerGenerator.GetItemContainerGeneratorForPanel(this); if (_itemContainerGenerator != null) { _itemContainerGenerator.ItemsChanged += new ItemsChangedEventHandler(OnItemsChanged); ((IItemContainerGenerator)_itemContainerGenerator).RemoveAll(); } } }

 

可以看到,这个方法会先调用静态方法ItemsControl.GetItemsOwner()获得这个Panel所处的ItemsControl。这个方法的定义如下:

//****************ItemsControl******************* 

/// <summary> /// Returns the ItemsControl for which element is an ItemsHost. /// More precisely, if element is marked by setting IsItemsHost="true" /// in the style for an ItemsControl, or if element is a panel created /// by the ItemsPresenter for an ItemsControl, return that ItemsControl. /// Otherwise, return null. /// </summary> public static ItemsControl GetItemsOwner(DependencyObject element) { ItemsControl container = null; Panel panel = element as Panel; if (panel != null && panel.IsItemsHost) { // see if element was generated for an ItemsPresenter ItemsPresenter ip = ItemsPresenter.FromPanel(panel); if (ip != null) { // if so use the element whose style begat the ItemsPresenter container = ip.Owner; } else { // otherwise use element's templated parent container = panel.TemplatedParent as ItemsControl; } } return container;
}

这个方法的注释已经说的很清楚了:在获取一个Panel所处的ItemsControl时,如果这个Panel的IsItemsHost属性非真则返回空值;不然,那么如果这个Panel的TemplateParent是ItemsPresenter,则返回其Owner,否则则直接返回这个Panel的TemplateParent。在知道自己所在的ItemsControl后,这个Panel就能调用这个ItemsControl的ItemContainerGenerator属性的GetItemContainerGeneratorForPanel()方法来获得一个正确的ItemContainerGenerator给其_itemContainerGenerator字段(Panel的Generator属性)赋值。

那么这里说的这个Panel是怎么出现在ItemsControl里的呢?要回答这个问题可以看一下ItemsControl的默认ItemsPanel模板:

        <ItemsPanelTemplate x:Key="ItemsPanelTemplate1">             <StackPanel IsItemsHost="True"/>         </ItemsPanelTemplate>

 此外,我们前面在介绍ItemsPanelTemplate时也提到过,ItemsControl.ItemsPanel属性的默认值就是StackPanel,这里重贴一下代码:

        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;         }

 

我们知道可以用作ItemsControl的ItemsPanel模板的控件基本都是Panel类的子类。而ItemsControl的默认ItemsPanel模板就用了StackPanel(此外,查看源代码可以看到ListBoxListView的默认ItemsPanel都是VirtualizingStackPanel,Menu类是WrapPanel,StatusBar类是DockPanel)。另外,要作为ItemDataTemplate生成的item container的容器,这个StackPanel的IsItemHost的值也必须为真。

结合第三篇文章的内容,这里我们按照从上至下的顺序梳理ItemsControl的模板应用机制:一个ItemsControl在应用模板时,首先会应用Template属性定义的模板生成自身的visual tree(这也是继承的Control类的模板机制),然后Template中的ItemsPresenter应用它的TemplateParent(即这个ItemsControl)的ItemsPanel模板生成一个visual tree,并把这个visual tree放置在这个ItemsPresenter的位置。ItemsPresenter只起一个占位符的作用,它不会定义自己的模板。特别的,要正确显示列表内容,ItemsPanel必须包含一个IsItemHost属性为真的面板。在ItemsPanel模板被应用时,这个面板会通过ConnectToGenerator()方法获得这个ItemsControl的ItemContainerGenrator,并调用其PrepareItemContainer()方法,并把这个ItemsContro的ItemTemplate模板装配到container相应的模板属性上,以供在呈现数据项(item)生成visual tree时使用。 

(本文完结)

(写的有点乱,待完善)

 

(感谢阅读,欢迎批评指正,转载请注明出处!)