dotnet/wpf · error · InvalidOperationException

UpdateDPI should only be called on the root of a Visual tree

Error message

UpdateDPI should only be called on the root of a Visual tree

What it means

VisualTreeHelper.SetRootDpi throws InvalidOperationException when the target visual has a parent (InternalVisualParent != null). Root DPI can only be set on the root of a visual tree; child DPI is derived from the parent, so setting it on a child would break the DPI inheritance invariant.

Solutions

  1. Call SetRootDpi only on the root visual (e.g. the visual obtained from HwndSource.RootVisual or a top-level Popup root)
  2. For child visuals, rely on automatic DPI inheritance or handle DpiChanged events instead
  3. Check visual parentage first: only call when VisualTreeHelper.GetParent(visual) is null

Example fix

// before
VisualTreeHelper.SetRootDpi(myUserControl, dpi); // throws: has parent
// after
var source = PresentationSource.FromVisual(myWindow) as HwndSource;
VisualTreeHelper.SetRootDpi(source.RootVisual, dpi);
Defensive patterns

Strategy: validation

Validate before calling

if (VisualTreeHelper.GetParent(visual) != null)
    throw new InvalidOperationException("SetRootDpi requires a root visual with no parent.");

Type guard

bool IsRootVisual(Visual v) => VisualTreeHelper.GetParent(v) == null;

Try / catch

try { VisualTreeHelper.SetRootDpi(visual, dpi); }
catch (InvalidOperationException ex) when (ex.Message.Contains("root")) { /* target the HwndSource.RootVisual instead */ }

Prevention

When it happens

Trigger: Calling VisualTreeHelper.SetRootDpi on any visual that is already attached to a parent — e.g. calling it inside a loaded element's DPI handling instead of on the HwndSource root visual.

Common situations: Trying to update DPI in a UserControl or child element; calling SetRootDpi from a window's content element rather than the root; handling DpiChanged at the wrong tree level.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/07507490f7d9436d. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/VisualTreeHelper.cs:110

        /// </summary>
        public static DpiScale GetDpi(Visual visual)
        {
            return visual.GetDpi();
        }


        /// <summary>
        /// This method updates the DPI information of a visual. It can only be called on a Visual with no parent.
        /// </summary>
        public static void SetRootDpi(Visual visual, DpiScale dpiInfo)
        {
            if ((object)dpiInfo == null)
            {
                throw new NullReferenceException("dpiInfo cannot be null");
            }
            if (visual.InternalVisualParent != null)
            {
                throw new InvalidOperationException("UpdateDPI should only be called on the root of a Visual tree");
            }
            DpiFlags dpiFlags = DpiUtil.UpdateDpiScalesAndGetIndex(dpiInfo.PixelsPerInchX, dpiInfo.PixelsPerInchY);
            visual.RecursiveSetDpiScaleVisualFlags(new DpiRecursiveChangeArgs(dpiFlags, visual.GetDpi(), dpiInfo));
        }

        /// <summary>
        /// Visual parent of this Visual.
        /// </summary>
        public static DependencyObject GetParent(DependencyObject reference)
        {
            Visual visual;
            Visual3D visual3D;

            VisualTreeUtils.AsNonNullVisual(reference, out visual, out visual3D);

            // x86 branch prediction skips the branch on first encounter.  We favor 2D.
            if (visual3D != null)
            {

View on GitHub (pinned to 81131a70a4)