Sfoglia il codice sorgente

Implement and make SharedSizeScopes work :)

Jumar Macato 6 anni fa
parent
commit
ad45848a29

+ 3 - 3
src/Avalonia.Controls/Grid/ColumnDefinition.cs

@@ -88,10 +88,10 @@ namespace Avalonia.Controls
             set { SetValue(WidthProperty, value); }
         }
 
-        internal override GridLength UserSize => this.Width;
+        internal override GridLength UserSizeValueCache => this.Width;
 
-        internal override double UserMinSize => this.MinWidth;
+        internal override double UserMinSizeValueCache => this.MinWidth;
 
-        internal override double UserMaxSize => this.MaxWidth;
+        internal override double UserMaxSizeValueCache => this.MaxWidth;
     }
 }

+ 263 - 23
src/Avalonia.Controls/Grid/DefinitionBase.cs

@@ -3,9 +3,7 @@
 
 using System;
 using System.Collections;
-using System.Collections.Generic;
 using System.Diagnostics;
-using Avalonia.Utilities;
 
 namespace Avalonia.Controls
 {
@@ -14,6 +12,26 @@ namespace Avalonia.Controls
     /// </summary>
     public abstract class DefinitionBase : AvaloniaObject
     {
+        /// <summary>
+        /// Static ctor. Used for static registration of properties.
+        /// </summary>
+        static DefinitionBase()
+        {
+            SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
+        }
+        internal bool UseSharedMinimum { get; set; }
+        internal bool LayoutWasUpdated { get; set; }
+        
+        private int _parentIndex = -1;                       //  this instance's index in parent's children collection
+        private LayoutTimeSizeType _sizeType;      //  layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content"
+        private double _minSize;                        //  used during measure to accumulate size for "Auto" and "Star" DefinitionBase's
+        private double _measureSize;                    //  size, calculated to be the input contstraint size for Child.Measure
+        private double _sizeCache;                      //  cache used for various purposes (sorting, caching, etc) during calculations
+        private double _offset;                         //  offset of the DefinitionBase from left / top corner (assuming LTR case)
+        internal SharedSizeScope _privateSharedSizeScope;
+        private SharedSizeState _sharedState;           //  reference to shared state object this instance is registered with
+        private bool _successUpdateSharedScope;
+
         /// <summary>
         /// Defines the <see cref="SharedSizeGroup"/> property.
         /// </summary>
@@ -28,31 +46,182 @@ namespace Avalonia.Controls
             get { return GetValue(SharedSizeGroupProperty); }
             set { SetValue(SharedSizeGroupProperty, value); }
         }
+        /// <summary>
+        /// Callback to notify about entering model tree.
+        /// </summary>
+        internal void OnEnterParentTree(Grid grid, int index)
+        {
+            Parent = grid;
+            _parentIndex = index;
+        }
+
+        internal void UpdateSharedScope()
+        {
+            if (_sharedState == null & 
+                SharedSizeGroup != null & 
+                Parent?.sharedSizeScope != null & 
+                !_successUpdateSharedScope)
+            {
+                _privateSharedSizeScope = Parent.sharedSizeScope;
+                _sharedState = _privateSharedSizeScope.EnsureSharedState(SharedSizeGroup);
+                _sharedState.AddMember(this);
+                _successUpdateSharedScope = true;
+            }
+        }
+
+        internal Grid Parent { get; set; }
 
         /// <summary>
-        /// Internal helper to access up-to-date UserSize property value.
+        /// Callback to notify about exitting model tree.
         /// </summary>
-        internal abstract GridLength UserSize { get; }
+        internal void OnExitParentTree()
+        {
+            _offset = 0;
+            if (_sharedState != null)
+            {
+                _sharedState.RemoveMember(this);
+                _sharedState = null;
+            }
+        }
 
         /// <summary>
-        /// Internal helper to access up-to-date UserMinSize property value.
+        /// Performs action preparing definition to enter layout calculation mode.
         /// </summary>
-        internal abstract double UserMinSize { get; }
+        internal void OnBeforeLayout(Grid grid)
+        {
+            if (SharedSizeGroup != null)
+                UpdateSharedScope();
+
+            //  reset layout state.
+            _minSize = 0;
+            LayoutWasUpdated = true;
+
+            //  defer verification for shared definitions
+            if (_sharedState != null)
+            {
+                _sharedState.EnsureDeferredValidation(grid);
+            }
+        }
 
         /// <summary>
-        /// Internal helper to access up-to-date UserMaxSize property value.
+        /// Updates min size.
+        /// </summary>
+        /// <param name="minSize">New size.</param>
+        internal void UpdateMinSize(double minSize)
+        {
+            _minSize = Math.Max(_minSize, minSize);
+        }
+
+        /// <summary>
+        /// Sets min size.
+        /// </summary>
+        /// <param name="minSize">New size.</param>
+        internal void SetMinSize(double minSize)
+        {
+            _minSize = minSize;
+        }
+
+        /// <remarks>
+        /// This method needs to be internal to be accessable from derived classes.
+        /// </remarks>
+        internal void OnUserSizePropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            _sharedState?.Invalidate();
+        }
+
+        /// <remarks>
+        /// This method needs to be internal to be accessable from derived classes.
+        /// </remarks>
+        internal static bool IsUserMinSizePropertyValueValid(object value)
+        {
+            double v = (double)value;
+            return (!double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v));
+        }
+
+        /// <remarks>
+        /// This method needs to be internal to be accessable from derived classes.
+        /// </remarks>
+        internal static void OnUserMaxSizePropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e)
+        {
+            Grid parentGrid = (Grid)definition.Parent;
+            parentGrid.InvalidateMeasure();
+
+        }
+
+        /// <remarks>
+        /// This method needs to be internal to be accessable from derived classes.
+        /// </remarks>
+        internal static bool IsUserMaxSizePropertyValueValid(object value)
+        {
+            double v = (double)value;
+            return (!double.IsNaN(v) && v >= 0.0d);
+        }
+
+        /// <summary>
+        /// Returns <c>true</c> if this definition is a part of shared group.
+        /// </summary>
+        internal bool IsShared
+        {
+            get { return (_sharedState != null); }
+        }
+
+        /// <summary>
+        /// Internal accessor to user size field.
+        /// </summary>
+        internal GridLength UserSize
+        {
+            get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); }
+        }
+
+        /// <summary>
+        /// Internal accessor to user min size field.
+        /// </summary>
+        internal double UserMinSize
+        {
+            get { return (UserMinSizeValueCache); }
+        }
+
+        /// <summary>
+        /// Internal accessor to user max size field.
         /// </summary>
-        internal abstract double UserMaxSize { get; }
+        internal double UserMaxSize
+        {
+            get { return (UserMaxSizeValueCache); }
+        }
+
+        /// <summary>
+        /// DefinitionBase's index in the parents collection.
+        /// </summary>
+        internal int Index
+        {
+            get
+            {
+                return (_parentIndex);
+            }
+            set
+            {
+                Debug.Assert(value >= -1 && _parentIndex != value);
+                _parentIndex = value;
+            }
+        }
 
         /// <summary>
         /// Layout-time user size type.
         /// </summary>
-        internal LayoutTimeSizeType SizeType { get; set; }
-        
+        internal LayoutTimeSizeType SizeType
+        {
+            get { return (_sizeType); }
+            set { _sizeType = value; }
+        }
+
         /// <summary>
         /// Returns or sets measure size for the definition.
         /// </summary>
-        internal double MeasureSize { get; set; }
+        internal double MeasureSize
+        {
+            get { return (_measureSize); }
+            set { _measureSize = value; }
+        }
 
         /// <summary>
         /// Returns definition's layout time type sensitive preferred size.
@@ -65,37 +234,108 @@ namespace Avalonia.Controls
             get
             {
                 double preferredSize = MinSize;
-                if (SizeType != LayoutTimeSizeType.Auto
-                    && preferredSize < MeasureSize)
+                if (_sizeType != LayoutTimeSizeType.Auto
+                    && preferredSize < _measureSize)
                 {
-                    preferredSize = MeasureSize;
+                    preferredSize = _measureSize;
                 }
-            return (preferredSize);
+                return (preferredSize);
             }
         }
 
         /// <summary>
         /// Returns or sets size cache for the definition.
         /// </summary>
-        internal double SizeCache { get; set; }
+        internal double SizeCache
+        {
+            get { return (_sizeCache); }
+            set { _sizeCache = value; }
+        }
 
         /// <summary>
-        /// Used during measure and arrange to accumulate size for "Auto" and "Star" DefinitionBase's
+        /// Returns min size.
         /// </summary>
-        internal double MinSize { get; set; }
+        internal double MinSize
+        {
+            get
+            {
+                double minSize = _minSize;
+                if (UseSharedMinimum
+                    && _sharedState != null
+                    && minSize < _sharedState.MinSize)
+                {
+                    minSize = _sharedState.MinSize;
+                }
+                return (minSize);
+            }
+        }
 
         /// <summary>
-        /// Updates min size.
+        /// Returns min size, always taking into account shared state.
         /// </summary>
-        /// <param name="minSize">New size.</param>
-        internal void UpdateMinSize(double minSize)
+        internal double MinSizeForArrange
         {
-            MinSize = Math.Max(MinSize, minSize);
+            get
+            {
+                double minSize = _minSize;
+                if (_sharedState != null
+                    && (UseSharedMinimum || !LayoutWasUpdated)
+                    && minSize < _sharedState.MinSize)
+                {
+                    minSize = _sharedState.MinSize;
+                }
+                return (minSize);
+            }
         }
 
         /// <summary>
         /// Offset.
         /// </summary>
-        internal double FinalOffset { get; set; }
+        internal double FinalOffset
+        {
+            get { return _offset; }
+            set { _offset = value; }
+        }
+
+        /// <summary>
+        /// Internal helper to access up-to-date UserSize property value.
+        /// </summary>
+        internal abstract GridLength UserSizeValueCache { get; }
+
+        /// <summary>
+        /// Internal helper to access up-to-date UserMinSize property value.
+        /// </summary>
+        internal abstract double UserMinSizeValueCache { get; }
+
+        /// <summary>
+        /// Internal helper to access up-to-date UserMaxSize property value.
+        /// </summary>
+        internal abstract double UserMaxSizeValueCache { get; }
+
+        private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e)
+        {
+            string sharedSizeGroupId = (string)e.NewValue;
+
+            if (definition._sharedState != null)
+            {
+                //  if definition is already registered AND shared size group id is changing,
+                //  then un-register the definition from the current shared size state object.
+                definition._sharedState.RemoveMember(definition);
+                definition._sharedState = null;
+            }
+
+            if ((definition._sharedState == null) && (sharedSizeGroupId != null))
+            {
+                var privateSharedSizeScope = definition._privateSharedSizeScope;
+                if (privateSharedSizeScope != null)
+                {
+                    //  if definition is not registered and both: shared size group id AND private shared scope 
+                    //  are available, then register definition.
+                    definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
+                    definition._sharedState.AddMember(definition);
+
+                }
+            }
+        }
     }
 }

+ 85 - 29
src/Avalonia.Controls/Grid/Grid.cs

@@ -86,6 +86,7 @@ namespace Avalonia.Controls
         private RowDefinitions _rowDefinitions;
         private DefinitionBase[] _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
         private DefinitionBase[] _definitionsV = new DefinitionBase[1] { new RowDefinition() };
+        internal SharedSizeScope sharedSizeScope;
 
         // 5 is an arbitrary constant chosen to end the measure loop
         private const int _layoutLoopMaxCount = 5;
@@ -171,6 +172,9 @@ namespace Avalonia.Controls
         static Grid()
         {
             ShowGridLinesProperty.Changed.AddClassHandler<Grid>(OnShowGridLinesPropertyChanged);
+            IsSharedSizeScopeProperty.Changed.AddClassHandler<Grid>(IsSharedSizeScopePropertyChanged);
+            BoundsProperty.Changed.AddClassHandler<Grid>(BoundsPropertyChanged);
+
             AffectsParentMeasure<Grid>(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
 
             _tempDefinitionsDataSlot = Thread.AllocateDataSlot();
@@ -181,6 +185,26 @@ namespace Avalonia.Controls
             _starWeightComparer = new StarWeightComparer();
         }
 
+        private static void BoundsPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs arg2)
+        {
+            for (int i = 0; i < grid._definitionsU.Length; i++)
+                grid._definitionsU[i].OnUserSizePropertyChanged(arg2);
+            for (int i = 0; i < grid._definitionsV.Length; i++)
+                grid._definitionsV[i].OnUserSizePropertyChanged(arg2);
+        }
+
+        private static void IsSharedSizeScopePropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e)
+        {
+            if ((bool)e.NewValue)
+            {
+                grid.sharedSizeScope = new SharedSizeScope();
+            }
+            else
+            {
+                grid.sharedSizeScope = null;
+            }
+        }
+
         /// <summary>
         /// Defines the Column attached property.
         /// </summary>
@@ -252,8 +276,12 @@ namespace Avalonia.Controls
                 if (_columnDefinitions.Count > 0)
                     _definitionsU = _columnDefinitions.Cast<DefinitionBase>().ToArray();
 
+                CallEnterParentTree(_definitionsU);
+
                 _columnDefinitions.CollectionChanged += delegate
                 {
+                    CallExitParentTree(_definitionsU);
+
                     if (_columnDefinitions.Count == 0)
                     {
                         _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
@@ -263,11 +291,26 @@ namespace Avalonia.Controls
                         _definitionsU = _columnDefinitions.Cast<DefinitionBase>().ToArray();
                         DefinitionsDirty = true;
                     }
+
+                    CallEnterParentTree(_definitionsU);
+
                     Invalidate();
                 };
             }
         }
 
+        private void CallEnterParentTree(DefinitionBase[] definitionsU)
+        {
+            for (int i = 0; i < definitionsU.Length; i++)
+                definitionsU[i].OnEnterParentTree(this, i);
+        }
+
+        private void CallExitParentTree(DefinitionBase[] definitionsU)
+        {
+            for (int i = 0; i < definitionsU.Length; i++)
+                definitionsU[i].OnExitParentTree();
+        }
+
         /// <summary>
         /// Gets or sets the row definitions for the grid.
         /// </summary>
@@ -294,6 +337,8 @@ namespace Avalonia.Controls
 
                 _rowDefinitions.CollectionChanged += delegate
                 {
+                    CallExitParentTree(_definitionsU);
+
                     if (_rowDefinitions.Count == 0)
                     {
                         _definitionsV = new DefinitionBase[1] { new RowDefinition() };
@@ -303,6 +348,8 @@ namespace Avalonia.Controls
                         _definitionsV = _rowDefinitions.Cast<DefinitionBase>().ToArray();
                         DefinitionsDirty = true;
                     }
+                    CallEnterParentTree(_definitionsU);
+
                     Invalidate();
                 };
             }
@@ -359,6 +406,16 @@ namespace Avalonia.Controls
             return element.GetValue(IsSharedSizeScopeProperty);
         }
 
+        /// <summary>
+        /// Sets the value of the IsSharedSizeScope attached property for a control.
+        /// </summary>
+        /// <param name="element">The control.</param>
+        /// <returns>The control's IsSharedSizeScope value.</returns>
+        public static void SetIsSharedSizeScope(AvaloniaObject element, bool value)
+        {
+            element.SetValue(IsSharedSizeScopeProperty, value);
+        }
+
         /// <summary>
         /// Sets the value of the Column attached property for a control.
         /// </summary>
@@ -659,7 +716,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Invalidates grid caches and makes the grid dirty for measure.
         /// </summary>
-        private void Invalidate()
+        internal void Invalidate()
         {
             CellsStructureDirty = true;
             InvalidateMeasure();
@@ -780,8 +837,7 @@ namespace Avalonia.Controls
         {
             for (int i = 0; i < definitions.Length; ++i)
             {
-                // Reset minimum size.
-                definitions[i].MinSize = 0;
+                definitions[i].OnBeforeLayout(this);
 
                 double userMinSize = definitions[i].UserMinSize;
                 double userMaxSize = definitions[i].UserMaxSize;
@@ -858,11 +914,11 @@ namespace Avalonia.Controls
                 {
                     if (isRows)
                     {
-                        _definitionsV[i].MinSize = minSizes[i];
+                        _definitionsV[i].SetMinSize(minSizes[i]);
                     }
                     else
                     {
-                        _definitionsU[i].MinSize = minSizes[i];
+                        _definitionsU[i].SetMinSize(minSizes[i]);
                     }
                 }
             }
@@ -1710,7 +1766,7 @@ namespace Avalonia.Controls
 
                     if (def.UserSize.IsStar)
                     {
-                        // Debug.Assert(!def.IsShared, "*-defs cannot be shared");
+                        Debug.Assert(!def.IsShared, "*-defs cannot be shared");
 
                         if (def.MeasureSize < 0.0)
                         {
@@ -1721,14 +1777,14 @@ namespace Avalonia.Controls
                             double starWeight = StarWeight(def, scale);
                             totalStarWeight += starWeight;
 
-                            if (def.MinSize > 0.0)
+                            if (def.MinSizeForArrange > 0.0)
                             {
                                 // store ratio w/min in MeasureSize (for now)
                                 definitionIndices[minCount++] = i;
-                                def.MeasureSize = starWeight / def.MinSize;
+                                def.MeasureSize = starWeight / def.MinSizeForArrange;
                             }
 
-                            double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize);
+                            double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize);
                             if (!double.IsPositiveInfinity(effectiveMaxSize))
                             {
                                 // store ratio w/max in SizeCache (for now)
@@ -1748,26 +1804,26 @@ namespace Avalonia.Controls
                                 break;
 
                             case (GridUnitType.Auto):
-                                userSize = def.MinSize;
+                                userSize = def.MinSizeForArrange;
                                 break;
                         }
 
                         double userMaxSize;
 
-                        // if (def.IsShared)
-                        // {
-                        //     //  overriding userMaxSize effectively prevents squishy-ness.
-                        //     //  this is a "solution" to avoid shared definitions from been sized to
-                        //     //  different final size at arrange time, if / when different grids receive
-                        //     //  different final sizes.
-                        //     userMaxSize = userSize;
-                        // }
-                        // else
-                        // {
-                        userMaxSize = def.UserMaxSize;
-                        // }
-
-                        def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize));
+                        if (def.IsShared)
+                        {
+                            //  overriding userMaxSize effectively prevents squishy-ness.
+                            //  this is a "solution" to avoid shared definitions from been sized to
+                            //  different final size at arrange time, if / when different grids receive
+                            //  different final sizes.
+                            userMaxSize = userSize;
+                        }
+                        else
+                        {
+                            userMaxSize = def.UserMaxSize;
+                        }
+
+                        def.SizeCache = Math.Max(def.MinSizeForArrange, Math.Min(userSize, userMaxSize));
                         takenSize += def.SizeCache;
                     }
                 }
@@ -1831,14 +1887,14 @@ namespace Avalonia.Controls
                     {
                         resolvedIndex = definitionIndices[minCount - 1];
                         resolvedDef = definitions[resolvedIndex];
-                        resolvedSize = resolvedDef.MinSize;
+                        resolvedSize = resolvedDef.MinSizeForArrange;
                         --minCount;
                     }
                     else
                     {
                         resolvedIndex = definitionIndices[defCount + maxCount - 1];
                         resolvedDef = definitions[resolvedIndex];
-                        resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
+                        resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize);
                         --maxCount;
                     }
 
@@ -1961,7 +2017,7 @@ namespace Avalonia.Controls
 
                     // min and max should have no effect by now, but just in case...
                     resolvedSize = Math.Min(resolvedSize, def.UserMaxSize);
-                    resolvedSize = Math.Max(def.MinSize, resolvedSize);
+                    resolvedSize = Math.Max(def.MinSizeForArrange, resolvedSize);
 
                     // Use the raw (unrounded) sizes to update takenSize, so that
                     // proportions are computed in the same terms as in phase 3;
@@ -2053,7 +2109,7 @@ namespace Avalonia.Controls
                         {
                             DefinitionBase definition = definitions[definitionIndices[i]];
                             double final = definition.SizeCache - dpiIncrement;
-                            final = Math.Max(final, definition.MinSize);
+                            final = Math.Max(final, definition.MinSizeForArrange);
                             if (final < definition.SizeCache)
                             {
                                 adjustedSize -= dpiIncrement;
@@ -2069,7 +2125,7 @@ namespace Avalonia.Controls
                         {
                             DefinitionBase definition = definitions[definitionIndices[i]];
                             double final = definition.SizeCache + dpiIncrement;
-                            final = Math.Max(final, definition.MinSize);
+                            final = Math.Max(final, definition.MinSizeForArrange);
                             if (final > definition.SizeCache)
                             {
                                 adjustedSize += dpiIncrement;

+ 3 - 3
src/Avalonia.Controls/Grid/RowDefinition.cs

@@ -89,10 +89,10 @@ namespace Avalonia.Controls
         }
 
 
-        internal override GridLength UserSize => this.Height;
+        internal override GridLength UserSizeValueCache => this.Height;
 
-        internal override double UserMinSize => this.MinHeight;
+        internal override double UserMinSizeValueCache => this.MinHeight;
 
-        internal override double UserMaxSize => this.MaxHeight;
+        internal override double UserMaxSizeValueCache => this.MaxHeight;
     }
 }

+ 43 - 0
src/Avalonia.Controls/Grid/SharedSizeScope.cs

@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Diagnostics;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Collection of shared states objects for a single scope
+    /// </summary>
+    internal class SharedSizeScope
+    {
+        /// <summary>
+        /// Returns SharedSizeState object for a given group.
+        /// Creates a new StatedState object if necessary.
+        /// </summary>
+        internal SharedSizeState EnsureSharedState(string sharedSizeGroup)
+        {
+            //  check that sharedSizeGroup is not default
+            Debug.Assert(sharedSizeGroup != null);
+
+            SharedSizeState sharedState = _registry[sharedSizeGroup] as SharedSizeState;
+            if (sharedState == null)
+            {
+                sharedState = new SharedSizeState(this, sharedSizeGroup);
+                _registry[sharedSizeGroup] = sharedState;
+            }
+            return (sharedState);
+        }
+
+        /// <summary>
+        /// Removes an entry in the registry by the given key.
+        /// </summary>
+        internal void Remove(object key)
+        {
+            Debug.Assert(_registry.Contains(key));
+            _registry.Remove(key);
+        }
+
+        private Hashtable _registry = new Hashtable();  //  storage for shared state objects
+    }
+}

+ 209 - 0
src/Avalonia.Controls/Grid/SharedSizeState.cs

@@ -0,0 +1,209 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Implementation of per shared group state object
+    /// </summary>
+    internal class SharedSizeState
+    {
+        private readonly SharedSizeScope _sharedSizeScope;  //  the scope this state belongs to
+        private readonly string _sharedSizeGroupId;         //  Id of the shared size group this object is servicing
+        private readonly List<DefinitionBase> _registry;    //  registry of participating definitions
+        private readonly EventHandler _layoutUpdated;       //  instance event handler for layout updated event
+        private Control _layoutUpdatedHost;               //  Control for which layout updated event handler is registered
+        private bool _broadcastInvalidation;                //  "true" when broadcasting of invalidation is needed
+        private bool _userSizeValid;                        //  "true" when _userSize is up to date
+        private GridLength _userSize;                       //  shared state
+        private double _minSize;                            //  shared state
+
+        /// <summary>
+        /// Default ctor.
+        /// </summary>
+        internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId)
+        {
+            Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null);
+            _sharedSizeScope = sharedSizeScope;
+            _sharedSizeGroupId = sharedSizeGroupId;
+            _registry = new List<DefinitionBase>();
+            _layoutUpdated = new EventHandler(OnLayoutUpdated);
+            _broadcastInvalidation = true;
+        }
+
+        /// <summary>
+        /// Adds / registers a definition instance.
+        /// </summary>
+        internal void AddMember(DefinitionBase member)
+        {
+            Debug.Assert(!_registry.Contains(member));
+            _registry.Add(member);
+            Invalidate();
+        }
+
+        /// <summary>
+        /// Removes / un-registers a definition instance.
+        /// </summary>
+        /// <remarks>
+        /// If the collection of registered definitions becomes empty
+        /// instantiates self removal from owner's collection.
+        /// </remarks>
+        internal void RemoveMember(DefinitionBase member)
+        {
+            Invalidate();
+            _registry.Remove(member);
+
+            if (_registry.Count == 0)
+            {
+                _sharedSizeScope.Remove(_sharedSizeGroupId);
+            }
+        }
+
+        /// <summary>
+        /// Propogates invalidations for all registered definitions.
+        /// Resets its own state.
+        /// </summary>
+        internal void Invalidate()
+        {
+            _userSizeValid = false;
+
+            if (_broadcastInvalidation)
+            {
+                for (int i = 0, count = _registry.Count; i < count; ++i)
+                {
+                    Grid parentGrid = (Grid)(_registry[i].Parent);
+                    parentGrid.Invalidate();
+                }
+                _broadcastInvalidation = false;
+            }
+        }
+
+        /// <summary>
+        /// Makes sure that one and only one layout updated handler is registered for this shared state.
+        /// </summary>
+        internal void EnsureDeferredValidation(Control layoutUpdatedHost)
+        {
+            if (_layoutUpdatedHost == null)
+            {
+                _layoutUpdatedHost = layoutUpdatedHost;
+                _layoutUpdatedHost.LayoutUpdated += _layoutUpdated;
+            }
+        }
+
+        /// <summary>
+        /// DefinitionBase's specific code.
+        /// </summary>
+        internal double MinSize
+        {
+            get
+            {
+                if (!_userSizeValid) { EnsureUserSizeValid(); }
+                return (_minSize);
+            }
+        }
+
+        /// <summary>
+        /// DefinitionBase's specific code.
+        /// </summary>
+        internal GridLength UserSize
+        {
+            get
+            {
+                if (!_userSizeValid) { EnsureUserSizeValid(); }
+                return (_userSize);
+            }
+        }
+
+        private void EnsureUserSizeValid()
+        {
+            _userSize = new GridLength(1, GridUnitType.Auto);
+
+            for (int i = 0, count = _registry.Count; i < count; ++i)
+            {
+                Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto
+                            || _userSize.GridUnitType == GridUnitType.Pixel);
+
+                GridLength currentGridLength = _registry[i].UserSizeValueCache;
+                if (currentGridLength.GridUnitType == GridUnitType.Pixel)
+                {
+                    if (_userSize.GridUnitType == GridUnitType.Auto)
+                    {
+                        _userSize = currentGridLength;
+                    }
+                    else if (_userSize.Value < currentGridLength.Value)
+                    {
+                        _userSize = currentGridLength;
+                    }
+                }
+            }
+            //  taking maximum with user size effectively prevents squishy-ness.
+            //  this is a "solution" to avoid shared definitions from been sized to
+            //  different final size at arrange time, if / when different grids receive
+            //  different final sizes.
+            _minSize = _userSize.IsAbsolute ? _userSize.Value : 0.0;
+
+            _userSizeValid = true;
+        }
+
+        /// <summary>
+        /// OnLayoutUpdated handler. Validates that all participating definitions
+        /// have updated min size value. Forces another layout update cycle if needed.
+        /// </summary>
+        private void OnLayoutUpdated(object sender, EventArgs e)
+        {
+            double sharedMinSize = 0;
+
+            //  accumulate min size of all participating definitions
+            for (int i = 0, count = _registry.Count; i < count; ++i)
+            {
+                sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize);
+            }
+
+            bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize);
+
+            //  compare accumulated min size with min sizes of the individual definitions
+            for (int i = 0, count = _registry.Count; i < count; ++i)
+            {
+                DefinitionBase definitionBase = _registry[i];
+
+                if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated)
+                {
+                    //  if definition's min size is different, then need to re-measure
+                    if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize))
+                    {
+                        Grid parentGrid = (Grid)definitionBase.Parent;
+                        parentGrid.InvalidateMeasure();
+                        definitionBase.UseSharedMinimum = true;
+                    }
+                    else
+                    {
+                        definitionBase.UseSharedMinimum = false;
+
+                        //  if measure is valid then also need to check arrange.
+                        //  Note: definitionBase.SizeCache is volatile but at this point 
+                        //  it contains up-to-date final size
+                        if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache))
+                        {
+                            Grid parentGrid = (Grid)definitionBase.Parent;
+                            parentGrid.InvalidateArrange();
+                        }
+                    }
+
+                    definitionBase.LayoutWasUpdated = false;
+                }
+            }
+
+            _minSize = sharedMinSize;
+
+            _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated;
+            _layoutUpdatedHost = null;
+
+            _broadcastInvalidation = true;
+        }
+    }
+}