File: System\Windows\Controls\GroupItem.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description: GroupItem object - root of the UI subtree generated for a CollectionViewGroup
//
// Specs:       Data Styling.mht
//
 
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using System.Collections.Generic;
using MS.Internal.Utility;
using MS.Internal.Hashing.PresentationFramework;
using System.Diagnostics;
using MS.Internal;
using System.Windows.Automation;
 
namespace System.Windows.Controls
{
    /// <summary>
    ///     A GroupItem appears as the root of the visual subtree generated for a CollectionViewGroup.
    /// </summary>
    public class GroupItem : ContentControl, IHierarchicalVirtualizationAndScrollInfo, IContainItemStorage
    {
        static GroupItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupItem), new FrameworkPropertyMetadata(typeof(GroupItem)));
            _dType = DependencyObjectType.FromSystemTypeInternal(typeof(GroupItem));
 
            // GroupItems should not be focusable by default
            FocusableProperty.OverrideMetadata(typeof(GroupItem), new FrameworkPropertyMetadata(false));
            AutomationProperties.IsOffscreenBehaviorProperty.OverrideMetadata(typeof(GroupItem), new FrameworkPropertyMetadata(IsOffscreenBehavior.FromClip));
        }
 
        /// <summary>
        /// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
        /// </summary>
        protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
        {
            return new System.Windows.Automation.Peers.GroupItemAutomationPeer(this);
        }
 
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
 
            _header = this.GetTemplateChild("PART_Header") as FrameworkElement;
 
            // GroupItem is generally re-templated to have an Expander.
            // Look for an Expander and store its Header size.g
            _expander = Helper.FindTemplatedDescendant<Expander>(this, this);
 
            //
            // ItemValueStorage:  restore saved values for this item onto the new container
            //
            if (_expander != null)
            {
                ItemsControl itemsControl = ParentItemsControl;
                if (itemsControl != null && VirtualizingPanel.GetIsVirtualizingWhenGrouping(itemsControl))
                {
                    Helper.SetItemValuesOnContainer(itemsControl, _expander, itemsControl.ItemContainerGenerator.ItemFromContainer(this));
                }
 
                _expander.Expanded += new RoutedEventHandler(OnExpanded);
            }
        }
 
        private static void OnExpanded(object sender, RoutedEventArgs e)
        {
            GroupItem groupItem = sender as GroupItem;
            if (groupItem != null && groupItem._expander != null && groupItem._expander.IsExpanded)
            {
                ItemsControl itemsControl = groupItem.ParentItemsControl;
                if (itemsControl != null && VirtualizingPanel.GetIsVirtualizing(itemsControl) && VirtualizingPanel.GetVirtualizationMode(itemsControl) == VirtualizationMode.Recycling)
                {
                    ItemsPresenter itemsHostPresenter = groupItem.ItemsHostPresenter;
                    if (itemsHostPresenter != null)
                    {
                        // In case a GroupItem that wasn't previously expanded is now
                        // recycled to represent an entity that is expanded, we face a situation
                        // where the ItemsHost isn't connected yet but we do need to synchronously
                        // remeasure the sub tree through the ItemsPresenter leading up to the
                        // ItemsHost panel. If we didnt do this the offsets could get skewed.
                        groupItem.InvalidateMeasure();
                        Helper.InvalidateMeasureOnPath(itemsHostPresenter, groupItem, false /*duringMeasure*/);
                    }
                }
            }
        }
 
        internal override void OnTemplateChangedInternal(FrameworkTemplate oldTemplate,FrameworkTemplate newTemplate)
        {
            base.OnTemplateChangedInternal(oldTemplate, newTemplate);
 
            if (_expander != null)
            {
                _expander.Expanded -= new RoutedEventHandler(OnExpanded);
                _expander = null;
            }
 
            _itemsHost = null;
        }
 
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            arrangeSize = base.ArrangeOverride(arrangeSize);
 
            Helper.ComputeCorrectionFactor(ParentItemsControl, this, ItemsHost, HeaderElement);
 
            return arrangeSize;
        }
 
        /// <summary>
        ///     Gives a string representation of this object.
        /// </summary>
        /// <returns></returns>
        internal override string GetPlainText()
        {
            System.Windows.Data.CollectionViewGroup cvg = Content as System.Windows.Data.CollectionViewGroup;
            if (cvg != null && cvg.Name != null)
            {
                return cvg.Name.ToString();
            }
 
            return base.GetPlainText();
        }
 
        //------------------------------------------------------
        //
        // Internal Properties
        //
        //------------------------------------------------------
 
        internal ItemContainerGenerator Generator
        {
            get { return _generator; }
            set { _generator = value; }
        }
 
        //------------------------------------------------------
        //
        // Internal Methods
        //
        //------------------------------------------------------
 
        internal void PrepareItemContainer(object item, ItemsControl parentItemsControl)
        {
            if (Generator == null)
                return;     // user-declared GroupItem - ignore (bug 108423)
 
            // If a GroupItem is being recycled set back IsItemsHost
            if (_itemsHost != null)
            {
                _itemsHost.IsItemsHost = true;
            }
 
            bool isVirtualizingWhenGrouping = (parentItemsControl != null && VirtualizingPanel.GetIsVirtualizingWhenGrouping(parentItemsControl));
 
            // Release any previous containers. Also ensures Items and GroupStyle are hooked up correctly
            if (Generator != null)
            {
                if (!isVirtualizingWhenGrouping)
                {
                    Generator.Release();
                }
                else
                {
                    Generator.RemoveAllInternal(true /*saveRecycleQueue*/);
                }
            }
 
            ItemContainerGenerator generator = Generator.Parent;
            GroupStyle groupStyle = generator.GroupStyle;
 
            // apply the container style
            Style style = groupStyle.ContainerStyle;
 
            // no ContainerStyle set, try ContainerStyleSelector
            if (style == null)
            {
                if (groupStyle.ContainerStyleSelector != null)
                {
                    style = groupStyle.ContainerStyleSelector.SelectStyle(item, this);
                }
            }
 
            // apply the style, if found
            if (style != null)
            {
                // verify style is appropriate before applying it
                if (!style.TargetType.IsInstanceOfType(this))
                    throw new InvalidOperationException(SR.Format(SR.StyleForWrongType, style.TargetType.Name, this.GetType().Name));
 
                this.Style = style;
                this.WriteInternalFlag2(InternalFlags2.IsStyleSetFromGenerator, true);
            }
 
            // forward the header template information
            if (ContentIsItem || !HasNonDefaultValue(ContentProperty))
            {
                this.Content = item;
                ContentIsItem = true;
            }
            if (!HasNonDefaultValue(ContentTemplateProperty))
                this.ContentTemplate = groupStyle.HeaderTemplate;
            if (!HasNonDefaultValue(ContentTemplateSelectorProperty))
                this.ContentTemplateSelector = groupStyle.HeaderTemplateSelector;
            if (!HasNonDefaultValue(ContentStringFormatProperty))
                this.ContentStringFormat = groupStyle.HeaderStringFormat;
 
            //
            // Clear previously cached items sizes
            //
            Helper.ClearVirtualizingElement(this);
 
            //
            // ItemValueStorage:  restore saved values for this item onto the new container
            //
            if (isVirtualizingWhenGrouping)
            {
                Helper.SetItemValuesOnContainer(parentItemsControl, this, item);
 
                if (_expander != null)
                {
                    Helper.SetItemValuesOnContainer(parentItemsControl, _expander, item);
                }
            }
        }
 
        internal void ClearItemContainer(object item, ItemsControl parentItemsControl)
        {
            if (Generator == null)
                return;     // user-declared GroupItem - ignore (bug 108423)
 
            //
            // ItemValueStorage:  save off values for this container if we're a virtualizing Group.
            //
            if (parentItemsControl != null && VirtualizingPanel.GetIsVirtualizingWhenGrouping(parentItemsControl))
            {
                Helper.StoreItemValues((IContainItemStorage)parentItemsControl, this, item);
 
                if (_expander != null)
                {
                    Helper.StoreItemValues((IContainItemStorage)parentItemsControl, _expander, item);
                }
 
                // Tell the panel to clear off all its containers.  This will cause this method to be called
                // recursively down the tree, allowing all descendent data to be stored before we save off
                // the ItemValueStorage DP for this container.
 
                VirtualizingPanel vp = _itemsHost as VirtualizingPanel;
                if (vp != null)
                {
                    vp.OnClearChildrenInternal();
                }
 
                Generator.RemoveAllInternal(true /*saveRecycleQueue*/);
            }
            else
            {
                Generator.Release();
            }
 
            ClearContentControl(item);
        }
 
        #region IHierarchicalVirtualizationAndScrollInfo
 
        HierarchicalVirtualizationConstraints IHierarchicalVirtualizationAndScrollInfo.Constraints
        {
            get { return HierarchicalVirtualizationConstraintsField.GetValue(this); }
            set
            {
                if (value.CacheLengthUnit == VirtualizationCacheLengthUnit.Page)
                {
                    throw new InvalidOperationException(SR.PageCacheSizeNotAllowed);
                }
                HierarchicalVirtualizationConstraintsField.SetValue(this, value);
            }
        }
 
        HierarchicalVirtualizationHeaderDesiredSizes IHierarchicalVirtualizationAndScrollInfo.HeaderDesiredSizes
        {
            get
            {
                FrameworkElement headerElement = HeaderElement;
                Size pixelHeaderSize = new Size();
 
                if (this.IsVisible && headerElement != null)
                {
                    pixelHeaderSize = headerElement.DesiredSize;
                    Helper.ApplyCorrectionFactorToPixelHeaderSize(ParentItemsControl, this, _itemsHost, ref pixelHeaderSize);
                }
 
                Size logicalHeaderSize = new Size(DoubleUtil.GreaterThanZero(pixelHeaderSize.Width) ? 1 : 0,
                                DoubleUtil.GreaterThanZero(pixelHeaderSize.Height) ? 1 : 0);
 
                return new HierarchicalVirtualizationHeaderDesiredSizes(logicalHeaderSize, pixelHeaderSize);
            }
        }
 
        HierarchicalVirtualizationItemDesiredSizes IHierarchicalVirtualizationAndScrollInfo.ItemDesiredSizes
        {
            get
            {
                return Helper.ApplyCorrectionFactorToItemDesiredSizes(this, _itemsHost);
            }
            set
            {
                HierarchicalVirtualizationItemDesiredSizesField.SetValue(this, value);
            }
        }
 
        Panel IHierarchicalVirtualizationAndScrollInfo.ItemsHost
        {
            get
            {
                return _itemsHost;
            }
        }
 
        bool IHierarchicalVirtualizationAndScrollInfo.MustDisableVirtualization
        {
            get { return MustDisableVirtualizationField.GetValue(this); }
            set { MustDisableVirtualizationField.SetValue(this, value); }
        }
 
        bool IHierarchicalVirtualizationAndScrollInfo.InBackgroundLayout
        {
            get { return InBackgroundLayoutField.GetValue(this); }
            set { InBackgroundLayoutField.SetValue(this, value); }
        }
 
        #endregion
 
        #region ItemValueStorage
 
 
        object IContainItemStorage.ReadItemValue(object item, DependencyProperty dp)
        {
            return Helper.ReadItemValue(this, item, dp.GlobalIndex);
        }
 
 
        void IContainItemStorage.StoreItemValue(object item, DependencyProperty dp, object value)
        {
            Helper.StoreItemValue(this, item, dp.GlobalIndex, value);
        }
 
        void IContainItemStorage.ClearItemValue(object item, DependencyProperty dp)
        {
            Helper.ClearItemValue(this, item, dp.GlobalIndex);
        }
 
        void IContainItemStorage.ClearValue(DependencyProperty dp)
        {
            Helper.ClearItemValueStorage(this, new int[] {dp.GlobalIndex});
        }
 
        void IContainItemStorage.Clear()
        {
            Helper.ClearItemValueStorage(this);
        }
 
        #endregion
 
        private ItemsControl ParentItemsControl
        {
            get
            {
                DependencyObject parent = this;
                do
                {
                    parent = VisualTreeHelper.GetParent(parent);
                    ItemsControl parentItemsControl = parent as ItemsControl;
                    if (parentItemsControl != null)
                    {
                        return parentItemsControl;
                    }
                } while (parent != null);
 
                return null;
            }
        }
 
        internal IContainItemStorage ParentItemStorageProvider
        {
            get
            {
                DependencyObject parentPanel = VisualTreeHelper.GetParent(this);
                if (parentPanel != null)
                {
                    DependencyObject owner = ItemsControl.GetItemsOwnerInternal(parentPanel);
                    return owner as IContainItemStorage;
                }
 
                return null;
            }
        }
 
        internal Panel ItemsHost
        {
            get
            {
                return _itemsHost;
            }
            set { _itemsHost = value; }
        }
 
        private ItemsPresenter ItemsHostPresenter
        {
            get
            {
                if (_expander != null)
                {
                    return Helper.FindTemplatedDescendant<ItemsPresenter>(_expander, _expander);
                }
                else
                {
                    return Helper.FindTemplatedDescendant<ItemsPresenter>(this, this);
                }
            }
        }
 
        internal Expander Expander { get { return _expander; } }
 
        private FrameworkElement ExpanderHeader
        {
            get
            {
                if (_expander != null)
                {
                    return _expander.GetTemplateChild(ExpanderHeaderPartName) as FrameworkElement;
                }
 
                return null;
            }
        }
 
        private FrameworkElement HeaderElement
        {
            get
            {
                FrameworkElement headerElement = null;
                if (_header != null)
                {
                    headerElement = _header;
                }
                else if (_expander != null)
                {
                    // Look for Expander. We special case for Expander since its a very common usage of grouping.
                    headerElement = ExpanderHeader;
                }
                return headerElement;
            }
        }
 
        //------------------------------------------------------
        //
        // Private Fields
        //
        //------------------------------------------------------
 
        ItemContainerGenerator _generator;
        private Panel _itemsHost;
        FrameworkElement _header;
        Expander _expander;
 
        internal static readonly UncommonField<bool> MustDisableVirtualizationField = new UncommonField<bool>();
        internal static readonly UncommonField<bool> InBackgroundLayoutField = new UncommonField<bool>();
 
        internal static readonly UncommonField<Thickness> DesiredPixelItemsSizeCorrectionFactorField = new UncommonField<Thickness>();
 
        internal static readonly UncommonField<HierarchicalVirtualizationConstraints> HierarchicalVirtualizationConstraintsField =
            new UncommonField<HierarchicalVirtualizationConstraints>();
        internal static readonly UncommonField<HierarchicalVirtualizationHeaderDesiredSizes> HierarchicalVirtualizationHeaderDesiredSizesField =
            new UncommonField<HierarchicalVirtualizationHeaderDesiredSizes>();
        internal static readonly UncommonField<HierarchicalVirtualizationItemDesiredSizes> HierarchicalVirtualizationItemDesiredSizesField =
            new UncommonField<HierarchicalVirtualizationItemDesiredSizes>();
 
        #region DTypeThemeStyleKey
 
        // Returns the DependencyObjectType for the registered ThemeStyleKey's default
        // value. Controls will override this method to return approriate types.
        internal override DependencyObjectType DTypeThemeStyleKey
        {
            get { return _dType; }
        }
 
        private static DependencyObjectType _dType;
 
        private const string ExpanderHeaderPartName = "HeaderSite";
 
        #endregion DTypeThemeStyleKey
    }
}