File: System\Windows\BroadcastEventHelper.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.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
using System.Windows.Documents;
using MS.Internal;
using MS.Internal.PresentationFramework;                   // SafeSecurityHelper
 
namespace System.Windows
{
    internal static class BroadcastEventHelper
    {
        /// <summary>
        /// Add the loaded callback to the MediaContext queue
        /// </summary>
        internal static void AddLoadedCallback(DependencyObject d, DependencyObject logicalParent)
        {
            Debug.Assert(d is FrameworkElement || d is FrameworkContentElement);
 
            DispatcherOperationCallback loadedCallback = new DispatcherOperationCallback(BroadcastEventHelper.BroadcastLoadedEvent);
 
            // Add the pending loaded event information to the MediaContext's pending
            // LoadedOrUnloadedCallbacks list so these can be called pre render
            LoadedOrUnloadedOperation loadedOp = MediaContext.From(d.Dispatcher).AddLoadedOrUnloadedCallback(loadedCallback, d);
 
            // Post to the dispatcher queue as a backup to fire the broadcast
            // event in case the tree change never triggers a Layout
            DispatcherOperation operation = d.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, loadedCallback, d);
 
            // Set the LoadedPending property
            d.SetValue(FrameworkElement.LoadedPendingPropertyKey, new object[]{loadedOp, operation, logicalParent});
        }
 
        /// <summary>
        /// Remove the loaded callback from the MediaContext queue
        /// </summary>
        internal static void RemoveLoadedCallback(DependencyObject d, object[] loadedPending)
        {
            Debug.Assert(d is FrameworkElement || d is FrameworkContentElement);
 
            if (loadedPending != null)
            {
                Debug.Assert(loadedPending.Length == 3);
 
                // Clear the LoadedPending property
                d.ClearValue(FrameworkElement.LoadedPendingPropertyKey);
 
                // If the dispatcher operation is pending abort it
                DispatcherOperation operation = (DispatcherOperation)loadedPending[1];
                if (operation.Status == DispatcherOperationStatus.Pending)
                {
                    operation.Abort();
                }
 
                // Remove the pending loaded information from the MediaContext's pending
                // LoadedOrUnloadedCallbacks list
                MediaContext.From(d.Dispatcher).RemoveLoadedOrUnloadedCallback((LoadedOrUnloadedOperation)loadedPending[0]);
            }
        }
 
        /// <summary>
        /// Add the unloaded callback to the MediaContext queue
        /// </summary>
        internal static void AddUnloadedCallback(DependencyObject d, DependencyObject logicalParent)
        {
            Debug.Assert(d is FrameworkElement || d is FrameworkContentElement);
 
            DispatcherOperationCallback unloadedCallback = new DispatcherOperationCallback(BroadcastEventHelper.BroadcastUnloadedEvent);
 
            // Add the pending unloaded event information to the MediaContext's pending
            // LoadedOrUnloadedCallbacks list so these can be called pre render
            LoadedOrUnloadedOperation unloadedOp = MediaContext.From(d.Dispatcher).AddLoadedOrUnloadedCallback(unloadedCallback, d);
 
            // Post to the dispatcher queue as a backup to fire the broadcast
            // event in case the tree change never triggers a Layout
            DispatcherOperation operation = d.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, unloadedCallback, d);
 
            // Set the UnloadedPending property
            d.SetValue(FrameworkElement.UnloadedPendingPropertyKey, new object[]{unloadedOp, operation, logicalParent});
        }
 
        /// <summary>
        /// Remove the unloaded callback from the MediaContext queue
        /// </summary>
        internal static void RemoveUnloadedCallback(DependencyObject d, object[] unloadedPending)
        {
            Debug.Assert(d is FrameworkElement || d is FrameworkContentElement);
 
            if (unloadedPending != null)
            {
                Debug.Assert(unloadedPending.Length == 3);
 
                // Clear the UnloadedPending property
                d.ClearValue(FrameworkElement.UnloadedPendingPropertyKey);
 
                // If the dispatcher operation is pending abort it
                DispatcherOperation operation = (DispatcherOperation)unloadedPending[1];
                if (operation.Status == DispatcherOperationStatus.Pending)
                {
                    operation.Abort();
                }
 
                // Remove the pending unloaded information from the MediaContext's pending
                // LoadedOrUnloadedCallbacks list
                MediaContext.From(d.Dispatcher).RemoveLoadedOrUnloadedCallback((LoadedOrUnloadedOperation)unloadedPending[0]);
            }
        }
 
        /// <summary>
        ///     Fire the [Loaded/Unloaded] broadcast events based upon the old and new parent values.
        ///     This method is called from ChangeLogicalParent() and OnVisualParentChanged().
        /// </summary>
        /// <param name="d">
        ///     Node to begin the broadcast
        /// </param>
        /// <param name="oldParent">
        ///     Old Parent
        /// </param>
        /// <param name="newParent">
        ///     New Parent
        /// </param>
        internal static void BroadcastLoadedOrUnloadedEvent(
            DependencyObject d,
            DependencyObject oldParent,
            DependencyObject newParent)
        {
            // Added to a tree
            if (oldParent == null && newParent != null)
            {
                if(IsLoadedHelper(newParent) == true)
                {
                    // Broadcast Loaded event if your new parent is loaded
                    // Note that this broadcast will take place when you are
                    // attached to your loaded visual parent
                    FireLoadedOnDescendentsHelper(d);
                }
            }
            // Removed from a tree
            else if (oldParent != null && newParent == null)
            {
                if (IsLoadedHelper(oldParent) == true)
                {
                    // Broadcast Unloaded event if your old parent was loaded
                    // Note that this broadcast will take place when you are
                    // detached from your loaded visual parent
                    FireUnloadedOnDescendentsHelper(d);
                }
            }
        }
 
        /// <summary>
        ///     Broadcast the Loaded event when UI is rendered and ready for user interaction.
        /// </summary>
        /// <param name="root">
        ///     Root of the sub-tree that the broadcast will start at
        /// </param>
        internal static object BroadcastLoadedEvent(object root)
        {
            DependencyObject rootDO = (DependencyObject)root;
 
            object[] loadedPending = (object[])rootDO.GetValue(FrameworkElement.LoadedPendingProperty);
 
            // The LoadedPendingProperty must be set if we have reached this far
            Debug.Assert(loadedPending != null && loadedPending.Length == 3,
                "The LoadedPendingProperty must be set if we have reached this far");
 
            bool isLoaded = IsLoadedHelper(rootDO);
 
            // Remove the Loaded callback from the MediaContext's queue
            RemoveLoadedCallback(rootDO, loadedPending);
 
            BroadcastLoadedSynchronously(rootDO, isLoaded);
 
            return null;
        }
 
        internal static void BroadcastLoadedSynchronously(DependencyObject rootDO, bool isLoaded)
        {
            // It is possible that the loaded broadcast for a parent caused you to be loaded before
            // your broadcast item got dequeued. In that case simply ignore the operation
            if (!isLoaded)
            {
                // Broadcast the Loaded event
                BroadcastEventHelper.BroadcastEvent(rootDO, FrameworkElement.LoadedEvent);
            }
        }
 
        /// <summary>
        ///     Broadcast the Unloaded event when the element is detached from a Loaded Tree
        /// </summary>
        /// <param name="root">
        ///     Root of the sub-tree that the broadcast will start at
        /// </param>
        internal static object BroadcastUnloadedEvent(object root)
        {
            DependencyObject rootDO = (DependencyObject)root;
 
            object[] unloadedPending = (object[])rootDO.GetValue(FrameworkElement.UnloadedPendingProperty);
 
            // The UnloadedPendingProperty must be set if we have reached this far
            Debug.Assert(unloadedPending != null && unloadedPending.Length == 3,
                "The UnloadedPendingProperty must be set if we have reached this far");
 
            bool isLoaded = IsLoadedHelper(rootDO);
 
            // Remove the Unloaded callback from the MediaContext's queue
            RemoveUnloadedCallback(rootDO, unloadedPending);
 
            BroadcastUnloadedSynchronously(rootDO, isLoaded);
 
            return null;
        }
 
        internal static void BroadcastUnloadedSynchronously(DependencyObject rootDO, bool isLoaded)
        {
            // It is possible that the unloaded broadcast for a parent caused you to be unloaded before
            // your broadcast item got dequeued. In that case simply ignore the operation
            if (isLoaded)
            {
                // Broadcast the Unloaded event
                BroadcastEvent(rootDO, FrameworkElement.UnloadedEvent);
            }
        }
 
        private static VisitedCallback<BroadcastEventData> BroadcastDelegate = new VisitedCallback<BroadcastEventData>(OnBroadcastCallback);
 
        private struct BroadcastEventData
        {
            internal BroadcastEventData(DependencyObject root, RoutedEvent routedEvent, List<DependencyObject> eventRoute)
            {
                Root = root;
                RoutedEvent = routedEvent;
                EventRoute = eventRoute;
            }
 
            internal DependencyObject       Root;
            internal RoutedEvent            RoutedEvent;
            internal List<DependencyObject> EventRoute;
        }
 
        /// <summary>
        ///     Broadcast the Loaded/Unloaded event in the sub-tree starting at the given root
        /// </summary>
        /// <param name="root">
        ///     Root of the sub-tree that the event will be broadcast to
        /// </param>
        /// <param name="routedEvent">
        ///     RoutedEventID for the event we wish to broadcast
        /// </param>
        private static void BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
        {
            // Broadcast to the tree and collect the set of nodes
            // on which we need fire the Loaded event
            List<DependencyObject> eventRoute = new List<DependencyObject>();
 
            // Create a DescendentsWalker for the broadcast
            DescendentsWalker<BroadcastEventData> walker = new DescendentsWalker<BroadcastEventData>(
                TreeWalkPriority.VisualTree, BroadcastDelegate, new BroadcastEventData(root, routedEvent, eventRoute));
 
            // Start the walk down
            walker.StartWalk(root);
 
            // Iterate and raise the event on each of the nodes in the tree
            for (int i=0; i< eventRoute.Count; i++)
            {
                DependencyObject d = eventRoute[i];
                RoutedEventArgs args = new RoutedEventArgs(routedEvent, d);
                FrameworkObject fo = new FrameworkObject(d, true /*throwIfNeither*/);
 
                if (routedEvent == FrameworkElement.LoadedEvent)
                {
                    fo.OnLoaded(args);
                }
                else
                {
                    fo.OnUnloaded(args);
                }
            }
        }
 
        // Callback on visiting each node in the descendency during a broadcast event
        private static bool OnBroadcastCallback(DependencyObject d, BroadcastEventData data, bool visitedViaVisualTree)
        {
            DependencyObject root = data.Root;
            RoutedEvent routedEvent = data.RoutedEvent;
            List<DependencyObject> eventRoute = data.EventRoute;
 
            // If this is a FrameworkElement            
            if (d is FrameworkElement fe)
            {
                if (fe != root && routedEvent == FrameworkElement.LoadedEvent && fe.UnloadedPending != null)
                {
                    // If there is a pending Unloaded event wait till we've broadcast 
                    // that event before we can fire the new Loaded event.
 
                    fe.FireLoadedOnDescendentsInternal();
                }
                else if (fe != root && routedEvent == FrameworkElement.UnloadedEvent && fe.LoadedPending != null)
                {
                    // If there is a pending Loaded event abort it because we are now 
                    // being Unloaded.
 
                    RemoveLoadedCallback(fe, fe.LoadedPending);
                }
                else
                {
                    if (fe != root)
                    {
                        if (routedEvent == FrameworkElement.LoadedEvent && fe.LoadedPending != null)
                        {
                            // If there is a pending Loaded event abort it because we are now 
                            // being Loaded.
                        
                            RemoveLoadedCallback(fe, fe.LoadedPending);
                        }
                        else if (routedEvent == FrameworkElement.UnloadedEvent && fe.UnloadedPending != null)
                        {
                            // If there is a pending Unloaded event abort it because we are now 
                            // being Unloaded.
                        
                            RemoveUnloadedCallback(fe, fe.UnloadedPending);
                        }
                    }
 
                    // If element has handlers fire the event and continue to walk down the tree
                    if (fe.SubtreeHasLoadedChangeHandler)
                    {
                        // We cannot assert this condition here for the following reason.
                        // If the [Un]LoadedHandler is added to the current node after the parent
                        // for this node has been [Un]Loaded but before the current node has been [Un]Loaded
                        // (example: within the [Un]Loaded handler for the parent), then the IsLoaded
                        // cache on the current node has been updated to match that of the parent,
                        // and this Assert will be violated. See BroadcastEventHelper.UpdateHasHandlerFlag
                        // for further description.
                        // Debug.Assert(IsLoaded == [false/true],
                        //     "Element should have been [Un]loaded before it is [Un]Loaded back again");
 
                        fe.IsLoadedCache = (routedEvent == FrameworkElement.LoadedEvent);
 
                        eventRoute.Add(fe);
                    
                        // Continue walk down subtree
                        return true;
                    }
                }
            }
            else
            {
                // If this is a FrameworkContentElement
                FrameworkContentElement fce = (FrameworkContentElement)d;
 
                if (fce != root && routedEvent == FrameworkElement.LoadedEvent && fce.UnloadedPending != null)
                {
                    // If there is a pending Unloaded event wait till we've broadcast 
                    // that event before we can fire the new Loaded event.
 
                    fce.FireLoadedOnDescendentsInternal();
                }
                else if (fce != root && routedEvent == FrameworkElement.UnloadedEvent && fce.LoadedPending != null)
                {
                    // If there is a pending Loaded event abort it because we are now 
                    // being Unloaded.
 
                    RemoveLoadedCallback(fce, fce.LoadedPending);
                }
                else
                {
                    if (fce != root)
                    {
                        if (routedEvent == FrameworkElement.LoadedEvent && fce.LoadedPending != null)
                        {
                            // If there is a pending Loaded event abort it because we are now 
                            // being Loaded.
                        
                            RemoveLoadedCallback(fce, fce.LoadedPending);
                        }
                        else if (routedEvent == FrameworkElement.UnloadedEvent && fce.UnloadedPending != null)
                        {
                            // If there is a pending Unloaded event abort it because we are now 
                            // being Unloaded.
                        
                            RemoveUnloadedCallback(fce, fce.UnloadedPending);
                        }
                    }
 
                    // If element has handlers fire the event and continue to walk down the tree
                    if (fce.SubtreeHasLoadedChangeHandler)
                    {
                        // We cannot assert this condition here for the following reason.
                        // If the [Un]LoadedHandler is added to the current node after the parent
                        // for this node has been [Un]Loaded but before the current node has been [Un]Loaded
                        // (example: within the [Un]Loaded handler for the parent), then the IsLoaded
                        // cache on the current node has been updated to match that of the parent,
                        // and this Assert will be violated. See BroadcastEventHelper.UpdateHasHandlerFlag
                        // for further description.
                        // Debug.Assert(IsLoaded == [false/true],
                        //     "Element should have been [Un]loaded before it is [Un]Loaded back again");
 
                        fce.IsLoadedCache = (routedEvent == FrameworkElement.LoadedEvent);
 
                        eventRoute.Add(fce);
 
                        // Continue walk down subtree
                        return true;
                    }
                }
            }
 
            // Stop walk down subtree
            return false;
        }
 
        private static bool SubtreeHasLoadedChangeHandlerHelper(DependencyObject d)
        {
            if (d is FrameworkElement fe)
            {
                return fe.SubtreeHasLoadedChangeHandler;
            }
            else if (d is FrameworkContentElement fce)
            {
                return fce.SubtreeHasLoadedChangeHandler;
            }
            return false;
        }
 
        private static void FireLoadedOnDescendentsHelper(DependencyObject d)
        {
            if (d is FrameworkElement fe)
            {
                fe.FireLoadedOnDescendentsInternal();
            }
            else
            {
                ((FrameworkContentElement)d).FireLoadedOnDescendentsInternal();
            }
        }
 
        private static void FireUnloadedOnDescendentsHelper(DependencyObject d)
        {
            if (d is FrameworkElement fe)
            {
                fe.FireUnloadedOnDescendentsInternal();
            }
            else
            {
                ((FrameworkContentElement)d).FireUnloadedOnDescendentsInternal();
            }
        }
 
        private static bool IsLoadedHelper(DependencyObject d)
        {
            FrameworkObject fo = new FrameworkObject(d);
            return fo.IsLoaded;
        }
 
        // Helper method that recursively queries the parents to see if they are loaded.
        // This method is invoked only when the loaded cache on the given node isn't valid.
        internal static bool IsParentLoaded(DependencyObject d)
        {
            FrameworkObject     fo      = new FrameworkObject(d);
            DependencyObject    parent  = fo.EffectiveParent;
            Visual              visual;
            Visual3D            visual3D;
 
            if (parent != null)
            {
                return IsLoadedHelper(parent);
            }
            else if ((visual = d as Visual) != null)
            {
                // If parent is null then this is the root element
                return SafeSecurityHelper.IsConnectedToPresentationSource(visual);
            }
            else if ((visual3D = d as Visual3D) != null)
            {
                // IsConnectedToPresentationSource could also be modified to take
                // a DO - instead though we'll just get the containing visual2D for
                // this 3D object.
                visual = VisualTreeHelper.GetContainingVisual2D(visual3D);
                if (visual != null)
                {
                    return SafeSecurityHelper.IsConnectedToPresentationSource(visual);
                }
                else
                {
                    return false;
                }
            }
            else
                return false;
        }
 
 
        /// <summary>
        ///     Check if the Framework Element Factory that produced the Template
        ///    that created this control has a Loaded Change Handler.
        /// </summary>
        /// <param name="templatedParent">
        ///     The caller must pass in a non-null templatedParent.
        /// </param>
        internal static FrameworkElementFactory GetFEFTreeRoot(DependencyObject templatedParent)
        {
            FrameworkObject fo = new FrameworkObject(templatedParent, true);
 
            Debug.Assert( fo.IsFE );
 
            FrameworkTemplate templatedParentTemplate = fo.FE.TemplateInternal;
            FrameworkElementFactory fefTree = templatedParentTemplate.VisualTree;
 
            return fefTree;
        }
 
        /// <summary>
        ///     Update the Has[Loaded/UnLoaded]Handler flags if required.
        ///     This method is called from OnNewParent/OnVisualParentChanged.
        /// </summary>
        /// <param name="d">
        ///     Node to begin the update
        /// </param>
        /// <param name="oldParent">
        ///     Old Parent
        /// </param>
        /// <param name="newParent">
        ///     New Parent
        /// </param>
        internal static void AddOrRemoveHasLoadedChangeHandlerFlag(
            DependencyObject d,
            DependencyObject oldParent,
            DependencyObject newParent)
        {
            bool hasLoadChangedHandler = SubtreeHasLoadedChangeHandlerHelper(d);
 
            if(hasLoadChangedHandler)
            {
                // Attaching to a Parent
                if (oldParent == null && newParent != null)
                {
                    // Subtree with a handler got added
                    AddHasLoadedChangeHandlerFlagInAncestry(newParent);
                }
 
                // Detaching from a Parent
                else if (oldParent != null && newParent == null)
                {
                    // Subtree with a handler got removed
                    RemoveHasLoadedChangeHandlerFlagInAncestry(oldParent);
                }
            }
        }
 
        internal static void AddHasLoadedChangeHandlerFlagInAncestry(DependencyObject d)
        {
            UpdateHasLoadedChangeHandlerFlagInAncestry(d, true);
        }
 
        internal static void RemoveHasLoadedChangeHandlerFlagInAncestry(DependencyObject d)
        {
            UpdateHasLoadedChangeHandlerFlagInAncestry(d, false);
        }
 
        /// <summary>
        ///     Evaluate the HasLoadedChangeHandler flag on the given node by
        ///   querying its children, and styles, and templates.
        /// </summary>
        /// <param name="fo">
        ///     Node
        /// </param>
        private static bool AreThereLoadedChangeHandlersInSubtree(ref FrameworkObject fo)
        {
            // HasHandler flag can be evaluated only for a FE/FCE.
            if (!fo.IsValid)
                return false;
 
            if (fo.ThisHasLoadedChangeEventHandler)
                return true;
 
            if (fo.IsFE)
            {
                // Check if any of your visual children have the flag set
                Visual v = (Visual)fo.FE;
                int count = VisualTreeHelper.GetChildrenCount(v);
 
                for(int i = 0; i < count; i++)
                {
                    FrameworkElement child = VisualTreeHelper.GetChild(v, i) as FrameworkElement;
                    if (child != null && child.SubtreeHasLoadedChangeHandler)
                    {
                        return true;
                    }
                }
            }
 
            // Check if any of your logical children have the flag set
            foreach(object o in LogicalTreeHelper.GetChildren(fo.DO))
            {
                DependencyObject child = o as DependencyObject;
                if(null != child && SubtreeHasLoadedChangeHandlerHelper(child))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        /// <summary>
        ///   This is a recursive function that walks up the tree Adding or Removing
        ///   HasLoadedChangeHander bits.   It also inits the IsLoadedCache on Add.
        /// </summary>
        /// <param name="d">
        ///     Node to update
        /// </param>
        /// <param name="addHandler">
        ///     Is it an AddHandler/ Add Child with Handler Operation
        /// </param>
        private static void UpdateHasLoadedChangeHandlerFlagInAncestry(DependencyObject d, bool addHandler)
        {
            FrameworkObject fo = new FrameworkObject(d);
 
            if (!addHandler)
            {
                if ( AreThereLoadedChangeHandlersInSubtree(ref fo) )
                    return;  // done
            }
 
            if (fo.IsValid)
            {
                if (fo.SubtreeHasLoadedChangeHandler != addHandler)
                {
                    DependencyObject coreParent = (fo.IsFE) ? VisualTreeHelper.GetParent(fo.FE) : null;
                    DependencyObject logicalParent = fo.Parent;
                    DependencyObject parent = null;
 
                    fo.SubtreeHasLoadedChangeHandler = addHandler;
 
                    // Propagate the change to your visual ancestry
                    if (coreParent != null)
                    {
                        UpdateHasLoadedChangeHandlerFlagInAncestry(coreParent, addHandler);
                        parent = coreParent;
                    }
 
                    // Propagate the change to your logical ancestry
                    if (logicalParent != null && logicalParent != coreParent)
                    {
                        UpdateHasLoadedChangeHandlerFlagInAncestry(logicalParent, addHandler);
                        if (fo.IsFCE)
                            parent = logicalParent;
                    }
 
                    // Propagate the change to your mentor, if any
                    if (logicalParent == null && coreParent == null)
                    {
                        parent = Helper.FindMentor(fo.DO.InheritanceContext);
                        if (parent != null)
                        {
                            fo.ChangeSubtreeHasLoadedChangedHandler(parent);
                        }
                    }
 
                    if(addHandler)
                    {
                        // The HasLoadedChangeHandler flag is used for two purposes.
                        // 1. To indicate that the sub-tree starting at the current node has
                        //    handlers for Loaded / Unloaded event.  So broadcast logic
                        //    can walk down that path to fire the events.
                        // 2. To indicate that the IsLoaded cache on the node is valid.
 
                        // If we are adding a handler:
                        // On the POP side of the recursion, as we come back down from the root,
                        // pull the value of IsLoadedCache from the parent in to the child.
                        if (fo.IsFE)
                        {
                            UpdateIsLoadedCache(fo.FE, parent);
                        }
                        else
                        {
                            UpdateIsLoadedCache(fo.FCE, parent);
                        }
                    }
                }
            }
            else  // neither a FE or an FCE
            {
                DependencyObject coreParent = null;
                Visual v;
                Visual3D v3D;
                ContentElement ce;
 
                // This is neither an FE nor and FCE
                // Propagate the change to your visual ancestry
                if ((v = d as Visual) != null)
                {
                    coreParent = VisualTreeHelper.GetParent(v);
                }
                else if ((ce = d as ContentElement) != null)
                {
                    coreParent = ContentOperations.GetParent(ce);
                }
                else if ((v3D = d as Visual3D) != null)
                {
                    coreParent = VisualTreeHelper.GetParent(v3D);
                }
 
                if (coreParent != null)
                {
                    UpdateHasLoadedChangeHandlerFlagInAncestry(coreParent, addHandler);
                }
            }
        }
 
        /// <summary>
        ///     Updates the IsLoadedCache on the current FrameworkElement
        /// </summary>
        private static void UpdateIsLoadedCache(
            FrameworkElement fe,
            DependencyObject parent)
        {
            if (fe.GetValue(FrameworkElement.LoadedPendingProperty) == null)
            {
                // Propagate the change to your visual ancestry
                if (parent != null)
                {
                    fe.IsLoadedCache = IsLoadedHelper(parent);
                }
 
                // This is the root visual.
                else if ( SafeSecurityHelper.IsConnectedToPresentationSource( fe ))
                {
                    fe.IsLoadedCache = true;
                }
                else
                {
                    fe.IsLoadedCache = false;
                }
            }
            else
            {
                // Clear the cache if Loaded is pending
                fe.IsLoadedCache = false;
            }
        }
 
        /// <summary>
        ///     Updates the IsLoadedCache on the current FrameworkContentElement
        /// </summary>
        private static void UpdateIsLoadedCache(
            FrameworkContentElement fce,
            DependencyObject        parent)
        {
            if (fce.GetValue(FrameworkElement.LoadedPendingProperty) == null)
            {
                // Propagate the change to your logical ancestry
                fce.IsLoadedCache = IsLoadedHelper(parent);
            }
            else
            {
                // Clear the cache if Loaded is pending
                fce.IsLoadedCache = false;
            }
        }
    }
}