File: System\Windows\ReverseInheritProperty.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// 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.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Input;
using MS.Internal;
 
namespace System.Windows
{
    internal abstract class ReverseInheritProperty
    {
        internal ReverseInheritProperty(
            DependencyPropertyKey flagKey,
            CoreFlags flagCache,
            CoreFlags flagChanged)
            : this(flagKey, flagCache, flagChanged, CoreFlags.None, CoreFlags.None)
        {
        }
 
        internal ReverseInheritProperty(
            DependencyPropertyKey flagKey,
            CoreFlags flagCache,
            CoreFlags flagChanged,
            CoreFlags flagOldOriginCache,
            CoreFlags flagNewOriginCache)
        {
            FlagKey = flagKey;
            FlagCache = flagCache;
            FlagChanged = flagChanged;
            FlagOldOriginCache = flagOldOriginCache;
            FlagNewOriginCache = flagNewOriginCache;
        }
 
        internal abstract void FireNotifications(UIElement uie, ContentElement ce, UIElement3D uie3D, bool oldValue);
 
        internal void OnOriginValueChanged(DependencyObject oldOrigin, DependencyObject newOrigin, ref DeferredElementTreeState oldTreeState)
        {
            OnOriginValueChanged(oldOrigin, newOrigin, null, ref oldTreeState, null);
        }
 
        internal void OnOriginValueChanged(DependencyObject oldOrigin,
            DependencyObject newOrigin,
            IList<DependencyObject> otherOrigins,
            ref DeferredElementTreeState oldTreeState,
            Action<DependencyObject, bool> originChangedAction)
        {
            DeferredElementTreeState treeStateLocalCopy = oldTreeState;
            oldTreeState = null;
 
            // Determine if one needs to raise notifications of elements
            // affected by the origin change irrespective of other origins
            bool setOriginCacheFlag = ((originChangedAction != null) &&
                                       (FlagOldOriginCache != CoreFlags.None) &&
                                       (FlagNewOriginCache != CoreFlags.None));
 
            // Step #1
            // Update the cache flags for all elements in the ancestry 
            // of the element that got turned off and record the changed nodes
            if (oldOrigin != null)
            {
                SetCacheFlagInAncestry(oldOrigin, false, treeStateLocalCopy, true, setOriginCacheFlag);
            }
 
            // Step #2
            // Update the cache flags for all elements in the ancestry 
            // of the element that got turned on and record the changed nodes
            if (newOrigin != null)
            {
                SetCacheFlagInAncestry(newOrigin, true, null, true, setOriginCacheFlag);
            }
            int otherCount = (otherOrigins != null) ? otherOrigins.Count : 0;
            for (int i = 0; i < otherCount; i++)
            {
                // setOriginCacheFlag is false, because these flags should not be affected by other origins
                SetCacheFlagInAncestry(otherOrigins[i], true, null, false, /*setOriginCacheFlag*/ false);
            }
 
            // Step #3
            // Fire value changed on elements in the ancestry of the element that got turned off.
            if (oldOrigin != null)
            {
                FirePropertyChangeInAncestry(oldOrigin, true /* oldValue */, treeStateLocalCopy, originChangedAction);
            }
 
            // Step #4
            // Fire value changed on elements in the ancestry of the element that got turned on.
            if (newOrigin != null)
            {
                FirePropertyChangeInAncestry(newOrigin, false /* oldValue */, null, originChangedAction);
            }
 
            if (oldTreeState == null && treeStateLocalCopy != null)
            {
                // Now that we have applied the old tree state, throw it away.
                treeStateLocalCopy.Clear();
                oldTreeState = treeStateLocalCopy;
            }
        }
 
        private void SetCacheFlagInAncestry(DependencyObject element,
            bool newValue,
            DeferredElementTreeState treeState,
            bool shortCircuit,
            bool setOriginCacheFlag)
        {
            UIElement uie;
            ContentElement ce;
            UIElement3D uie3D;
            CastElement(element, out uie, out ce, out uie3D);
 
            bool isFlagSet = IsFlagSet(uie, ce, uie3D, FlagCache);
            bool isFlagOriginCacheSet = (setOriginCacheFlag ? IsFlagSet(uie, ce, uie3D, (newValue ? FlagNewOriginCache : FlagOldOriginCache)) : false);
 
            // If the cache flag value is undergoing change, record it and
            // propagate the change to the ancestors.
            if ((newValue != isFlagSet) ||
                (setOriginCacheFlag && !isFlagOriginCacheSet) ||
                !shortCircuit)
            {
                if (newValue != isFlagSet)
                {
                    SetFlag(uie, ce, uie3D, FlagCache, newValue);
 
                    // NOTE: we toggle the changed flag instead of setting it so that that way common 
                    // ancestors show resultant unchanged and do not receive any change notification.
                    SetFlag(uie, ce, uie3D, FlagChanged, !IsFlagSet(uie, ce, uie3D, FlagChanged));
                }
 
                if (setOriginCacheFlag && !isFlagOriginCacheSet)
                {
                    SetFlag(uie, ce, uie3D, (newValue ? FlagNewOriginCache : FlagOldOriginCache), true);
                }
 
                // Check for block reverse inheritance flag, elements like popup want to set this.
                if (BlockReverseInheritance(uie, ce, uie3D))
                {
                    return;
                }
 
                // Propagate the flag up the visual and logical trees.  Note our
                // minimal optimization check to avoid walking both the core
                // and logical parents if they are the same.
                {
                    DependencyObject coreParent = DeferredElementTreeState.GetInputElementParent(element, treeState);
                    DependencyObject logicalParent = DeferredElementTreeState.GetLogicalParent(element, treeState);
                    
                    if (coreParent != null)
                    {
                        SetCacheFlagInAncestry(coreParent, newValue, treeState, shortCircuit, setOriginCacheFlag);
                    }                
                    if (logicalParent != null && logicalParent != coreParent)
                    {
                        SetCacheFlagInAncestry(logicalParent, newValue, treeState, shortCircuit, setOriginCacheFlag);
                    }
                }
            }
        }
 
        private void FirePropertyChangeInAncestry(DependencyObject element,
            bool oldValue,
            DeferredElementTreeState treeState,
            Action<DependencyObject, bool> originChangedAction)
        {
            UIElement uie;
            ContentElement ce;
            UIElement3D uie3D;
            CastElement(element, out uie, out ce, out uie3D);
 
            bool flagChanged = IsFlagSet(uie, ce, uie3D, FlagChanged);
            bool isFlagOldOriginCacheSet = ((FlagOldOriginCache == CoreFlags.None) ? false : IsFlagSet(uie, ce, uie3D, FlagOldOriginCache));
            bool isFlagNewOriginCacheSet = ((FlagNewOriginCache == CoreFlags.None) ? false : IsFlagSet(uie, ce, uie3D, FlagNewOriginCache));
 
            if (flagChanged || isFlagOldOriginCacheSet || isFlagNewOriginCacheSet)
            {
                if (flagChanged)
                {
                    // if FlagChanged bit is set, then the value has changed effectively
                    // after considering all the origins. Hence change the property value
                    // and fire notifications.
                    SetFlag(uie, ce, uie3D, FlagChanged, false);
 
                    if (oldValue)
                    {
                        element.ClearValue(FlagKey);
                    }
                    else
                    {
                        element.SetValue(FlagKey, true);
                    }
 
                    FireNotifications(uie, ce, uie3D, oldValue);
                }
 
                if (isFlagOldOriginCacheSet || isFlagNewOriginCacheSet)
                {
                    SetFlag(uie, ce, uie3D, FlagOldOriginCache, false);
                    SetFlag(uie, ce, uie3D, FlagNewOriginCache, false);
                    if (isFlagOldOriginCacheSet != isFlagNewOriginCacheSet)
                    {
                        // if either FlagOldOriginCache or FlagNewOriginCache
                        // are set, then the origin change has affected this node
                        // and hence originChangedAction should be executed.
                        Debug.Assert(originChangedAction != null);
                        originChangedAction(element, oldValue);
                    }
                }
 
                // Check for block reverse inheritance flag, elements like popup want to set this.
                if (BlockReverseInheritance(uie, ce, uie3D))
                {
                    return;
                }
 
                // Call FirePropertyChange up the visual and logical trees.
                // Note our minimal optimization check to avoid walking both
                // the core and logical parents if they are the same.
                {
                    DependencyObject coreParent = DeferredElementTreeState.GetInputElementParent(element, treeState);
                    DependencyObject logicalParent = DeferredElementTreeState.GetLogicalParent(element, treeState);
 
                    if (coreParent != null)
                    {
                        FirePropertyChangeInAncestry(coreParent, oldValue, treeState, originChangedAction);
                    }
                    if (logicalParent != null && logicalParent != coreParent)
                    {
                        FirePropertyChangeInAncestry(logicalParent, oldValue, treeState, originChangedAction);
                    }
                }
            }
        }
 
        /////////////////////////////////////////////////////////////////////
 
        private static bool BlockReverseInheritance(UIElement uie, ContentElement ce, UIElement3D uie3D)
        {
            if (uie != null)
            {
                return uie.BlockReverseInheritance();
            }
            else if (ce != null)
            {
                return ce.BlockReverseInheritance();
            }
            else if (uie3D != null)
            {
                return uie3D.BlockReverseInheritance();
            }
 
            return false;
        }
 
        /////////////////////////////////////////////////////////////////////
 
        private static void SetFlag(UIElement uie, ContentElement ce, UIElement3D uie3D, CoreFlags flag, bool value)
        {
            if (uie != null)
            {
                uie.WriteFlag(flag, value);
            }
            else if (ce != null)
            {
                ce.WriteFlag(flag, value);
            }
            else if (uie3D != null)
            {
                uie3D.WriteFlag(flag, value);
            }
        }
 
        /////////////////////////////////////////////////////////////////////
 
        private static bool IsFlagSet(UIElement uie, ContentElement ce, UIElement3D uie3D, CoreFlags flag)
        {
            if (uie != null)
            {
                return uie.ReadFlag(flag);
            }
            else if (ce != null)
            {
                return ce.ReadFlag(flag);
            }
            else if (uie3D != null)
            {
                return uie3D.ReadFlag(flag);
            }
 
            return false;
        }
 
        /////////////////////////////////////////////////////////////////////
 
        private static void CastElement(DependencyObject o, out UIElement uie, out ContentElement ce, out UIElement3D uie3D)
        {
            uie = o as UIElement;
            ce = (uie != null) ? null : o as ContentElement;
            uie3D = (uie != null || ce != null) ? null : o as UIElement3D;
        }
 
        /////////////////////////////////////////////////////////////////////
 
        protected DependencyPropertyKey FlagKey;
        protected CoreFlags FlagCache;
        protected CoreFlags FlagChanged;
        protected CoreFlags FlagOldOriginCache; // Flag to keep track of elements in the path of old origin
        protected CoreFlags FlagNewOriginCache; // Flag to keep track of elements in the path of new origin
    }
}