| File: System\Windows\Diagnostics\VisualDiagnostics.cs | Web Access |
| Project: src\wpf\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. // Description: // Visual tree diagnostic API. using Microsoft.Win32; using MS.Internal; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Media3D; namespace System.Windows.Diagnostics { public static class VisualDiagnostics { #pragma warning disable 649 // Warning CS0649: The Field 'VisualDiagnostics.s_isDebuggerCheckDisabledForTestPurposes' is never // assigned to, and will always have its default value false // // This field exists for test purposes private static bool s_isDebuggerCheckDisabledForTestPurposes; #pragma warning restore 649 private static readonly bool s_IsEnabled; private static event EventHandler<VisualTreeChangeEventArgs> s_visualTreeChanged; private static bool s_HasVisualTreeChangedListeners; [ThreadStatic] private static bool s_IsVisualTreeChangedInProgress; [ThreadStatic] private static HwndSource s_ActiveHwndSource; static VisualDiagnostics() { s_IsEnabled = !CoreAppContextSwitches.DisableDiagnostics; } #region Public /// <summary> /// Visual tree change notification. Fires for any visual change regardless whether it is /// connected to any root visual, e.g. Window. Subscribers are responsible for performing /// necessary filtering. /// </summary> /// <remarks> /// This event is for diagnostic use only. Handlers of this event should /// limit themselves to read-only access to elements, properties, and resources. /// /// Microsoft does not support the use of this event in a production application /// under any circumstance. /// </remarks> public static event EventHandler<VisualTreeChangeEventArgs> VisualTreeChanged { add { if (EnableHelper.IsVisualTreeChangeEnabled) { s_visualTreeChanged += value; s_HasVisualTreeChangedListeners = true; } } remove { s_visualTreeChanged -= value; } } /// <summary> /// Enable the VisualTreeChanged event. /// </summary> /// <remarks> /// This method is for diagnostic use only. /// Microsoft does not support the use of this method in a production application /// under any circumstance. /// </remarks> public static void EnableVisualTreeChanged() { EnableHelper.EnableVisualTreeChanged(); } /// <summary> /// Disable the VisualTreeChanged event. /// </summary> public static void DisableVisualTreeChanged() { EnableHelper.DisableVisualTreeChanged(); } /// <summary> /// Provides object source info which will be available for objects created from BAML or XAML. /// Source info will not be available if diagnostics have not been enabled at the time of /// loading BAML or XAML. /// </summary> public static XamlSourceInfo GetXamlSourceInfo(object obj) { return XamlSourceInfoHelper.GetXamlSourceInfo(obj); } #endregion Public internal static void OnVisualChildChanged(DependencyObject parent, DependencyObject child, bool isAdded) { EventHandler<VisualTreeChangeEventArgs> visualTreeChanged = VisualDiagnostics.s_visualTreeChanged; if (visualTreeChanged != null && EnableHelper.IsVisualTreeChangeEnabled) { int index; VisualTreeChangeType changeType; if (isAdded) { index = VisualDiagnostics.GetChildIndex(parent, child); changeType = VisualTreeChangeType.Add; } else { // We cannot reliably get correct child index for a removed child. We'll force it to be -1; index = -1; changeType = VisualTreeChangeType.Remove; } RaiseVisualTreeChangedEvent( visualTreeChanged, new VisualTreeChangeEventArgs(parent, child, index, changeType), // see EnableHelper.IsChangePermitted isPotentialOuterChange: (changeType==VisualTreeChangeType.Add && index==0 && VisualTreeHelper.GetParent(parent) == null)); } } private static void RaiseVisualTreeChangedEvent( EventHandler<VisualTreeChangeEventArgs> visualTreeChanged, VisualTreeChangeEventArgs args, bool isPotentialOuterChange) { bool savedIsVisualTreeChangedInProgress = s_IsVisualTreeChangedInProgress; HwndSource savedActiveHwndSource = s_ActiveHwndSource; try { s_IsVisualTreeChangedInProgress = true; if (isPotentialOuterChange) { s_ActiveHwndSource = PresentationSource.FromDependencyObject(args.Parent) as System.Windows.Interop.HwndSource; } visualTreeChanged(null, args); } finally { s_IsVisualTreeChangedInProgress = savedIsVisualTreeChangedInProgress; s_ActiveHwndSource = savedActiveHwndSource; } } private static int GetChildIndex(DependencyObject parent, DependencyObject child) { int index = -1; Visual asVisual = child as Visual; if (asVisual != null) { index = asVisual._parentIndex; } else { Visual3D asVisual3D = child as Visual3D; if (asVisual3D != null) { index = asVisual3D.ParentIndex; } } // Sometimes index is not up to date. We'll have to find it manually. if (index < 0) { int count = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < count; i++) { DependencyObject obj = VisualTreeHelper.GetChild(parent, i); if (obj == child) { index = i; break; } } } return index; } internal static bool IsEnabled { get { return s_IsEnabled;} } // detect whether a VisualTreeChanged event is in progress. If so, // throw an exception unless overridden by the app-context flag. internal static void VerifyVisualTreeChange(DependencyObject d) { // write this so that the 90% case is inlined - check the flag and move on if (s_HasVisualTreeChangedListeners) { VerifyVisualTreeChangeCore(d); } } private static void VerifyVisualTreeChangeCore(DependencyObject d) { if (s_IsVisualTreeChangedInProgress) { if (!EnableHelper.AllowChangesDuringVisualTreeChanged(d)) { throw new InvalidOperationException(SR.Format(SR.ReentrantVisualTreeChangeError, nameof(VisualTreeChanged))); } } } internal static bool IsEnvironmentVariableSet(string value, string environmentVariable) { if (value != null) { return IsEnvironmentValueSet(value); } value = Environment.GetEnvironmentVariable(environmentVariable); return IsEnvironmentValueSet(value); } internal static bool IsEnvironmentValueSet(string value) { value = (value ?? string.Empty).Trim().ToLowerInvariant(); return !(value == string.Empty || value == "0" || value == "false"); } // this class does all the work for checking whether VisualTreeChanged features are // enabled, disallowed, etc. It's a separate class so that its static // cctor doesn't run until needed, giving apps time to initialize factors // that influence the decisions: environment, registry, app-context switches, etc. private static class EnableHelper { static EnableHelper() { if (IsEnabled) { s_IsDevMode = GetDevModeFromRegistry(); s_IsEnableVisualTreeChangedAllowed = PrecomputeIsEnableVisualTreeChangedAllowed(); } } internal static void EnableVisualTreeChanged() { if (!IsEnableVisualTreeChangedAllowed) throw new InvalidOperationException(SR.Format(SR.MethodCallNotAllowed, nameof(VisualDiagnostics.EnableVisualTreeChanged))); s_IsVisualTreeChangedEnabled = true; } internal static void DisableVisualTreeChanged() { s_IsVisualTreeChangedEnabled = false; } internal static bool IsVisualTreeChangeEnabled { get { return IsEnabled && (s_IsVisualTreeChangedEnabled || System.Diagnostics.Debugger.IsAttached || s_isDebuggerCheckDisabledForTestPurposes); } } internal static bool AllowChangesDuringVisualTreeChanged(DependencyObject d) { if (s_AllowChangesDuringVisualTreeChanged == null) { if (IsChangePermitted(d)) return true; s_AllowChangesDuringVisualTreeChanged = CoreAppContextSwitches.AllowChangesDuringVisualTreeChanged; if (s_AllowChangesDuringVisualTreeChanged == true) { // user wants to allow re-entrant changes, and this is the first one. // Issue a warning to debug output System.Diagnostics.Debug.WriteLine(SR.Format(SR.ReentrantVisualTreeChangeWarning, nameof(VisualTreeChanged))); } return (s_AllowChangesDuringVisualTreeChanged == true); } else { return (s_AllowChangesDuringVisualTreeChanged == true) || IsChangePermitted(d); } } // For compat, allow nested changes when: // a. The outer change added a child to an element that is a visual // root of a window. (More precisely - the element has no parent, // but does have an HwndSource.) // b. The inner change affects an element belonging to a different // PresentationSource. // // Handlers for VisualTreeChanged should not cause side-effects - that // creates a situation where the app can behave differently during // diagnosis than it does in production. But some already-shipped // diagnostic assistants cause side-effects in the situation above. // [VS 2015 and 2017 create a new window to hold the "little box" // containing the diagnostic icons/buttons. They should have done this // asynchronously, outside the scope of VisualTreeChanged, but they // already shipped with this flaw.] // private static bool IsChangePermitted(DependencyObject d) { // if the outer change was type (a), OnVisualChildChanged saved // the presentation source in s_ActiveHwndSource return (s_ActiveHwndSource != null) && (d != null) && (s_ActiveHwndSource != PresentationSource.FromDependencyObject(d)); } /// <summary> /// EnableVisualTreeChanged can be called only in certain scenarios. /// Here we precompute the parts of the rule that can't change at runtime. /// </summary> private static bool? PrecomputeIsEnableVisualTreeChangedAllowed() { if (!IsEnabled) return false; // if diagnostics are disabled, not allowed if (IsDevMode) return true; // if DevMode is on, allowed if (IsEnvironmentVariableSet(null, c_enableVisualTreeNotificationsEnvironmentVariable)) return true; // if environment variable is set, allowed return null; // otherwise, need to check at runtime (for debugger attached, etc.) } /// <summary> /// read the registry to see if Win10 Dev Mode is set /// </summary> private static bool GetDevModeFromRegistry() { RegistryKey key = Registry.LocalMachine.OpenSubKey(c_devmodeRegKey); if (key != null) { using (key) { object obj = key.GetValue(c_devmodeValueName); if (obj is int) { return ((int)obj != 0); } } } return false; } private static bool IsDevMode { get { return s_IsDevMode; } } private static bool IsEnableVisualTreeChangedAllowed { get { return s_IsEnableVisualTreeChangedAllowed ?? System.Diagnostics.Debugger.IsAttached; } } private static readonly bool s_IsDevMode; private static readonly bool? s_IsEnableVisualTreeChangedAllowed; private static bool s_IsVisualTreeChangedEnabled; private static bool? s_AllowChangesDuringVisualTreeChanged; private const string c_enableVisualTreeNotificationsEnvironmentVariable = "ENABLE_XAML_DIAGNOSTICS_VISUAL_TREE_NOTIFICATIONS"; private const string c_devmodeRegKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"; private const string c_devmodeRegKeyFullPath = @"HKEY_LOCAL_MACHINE\" + c_devmodeRegKey; private const string c_devmodeValueName = "AllowDevelopmentWithoutDevLicense"; } } }