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
- Call SetRootDpi only on the root visual (e.g. the visual obtained from HwndSource.RootVisual or a top-level Popup root)
- For child visuals, rely on automatic DPI inheritance or handle DpiChanged events instead
- 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
- Only call SetRootDpi on HwndSource.RootVisual or other parentless roots
- Handle DPI changes via DpiChanged events on the top-level source, not per-child calls
- Remember child visuals inherit DPI; never set it manually on children
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
- ' ' is not a Visual or Visual3D.
- Animation_Invalid_DefaultValue
- Cannot remove signature from read-only file.
- dpiInfo cannot be null
- Image_EncoderNoColorContext
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)