Repository: dotnet/winforms (2025-08-08T22:08:34Z)

Issue:
Broken rendering with BackgroundImage and transparent BackColor
### .NET version

.NET 5.0 - .NET 9.0

### Did it work in .NET Framework?

Yes

### Did it work in any of the earlier releases of .NET Core or .NET 5+?

Works in .NET Framework and .NET Core 3.x

### Issue description

Set a background image and transparent background for a non-root level container object, such as a `GroupBox` or a `Panel`, and add child controls to it. While the background image offset is applied correctly to the child controls, this is not true for the 'foreground' content of the parent, which is reflected in every child control with an incorrect origin.

It apparently affects system rendering only (i.e. when `FlatStyle` is `System`), or when a control has no `FlatStyle` (e.g. a `Panel`).

Consider the example below:

Designer:
<img width="890" height="281" alt="Image" src="https://github.com/user-attachments/assets/ce5a6964-1671-49f3-9c63-9376493f93be" />

Rendering in .NET Framework:
<img width="365" height="235" alt="Image" src="https://github.com/user-attachments/assets/45549f09-d3ee-40ed-97f8-209dda8965cd" />

Rendering in .NET Core/.NET:
<img width="426" height="266" alt="Image" src="https://github.com/user-attachments/assets/1bd693a9-07b9-4072-a01d-402433d905c3" />
(Note the "ghost" group boxes appearing in the panel and other controls. The image itself is rendered correctly in the children even if they are over a more colorful area.)

### Steps to reproduce

See the attachment:

[BackgroundImageIssue.zip](https://github.com/user-attachments/files/21613643/BackgroundImageIssue.zip)


Discussion:
This issue also recurs when `GroupBox.Enable = false` && `BackColor = Color.Transparent;` is set.

This is caused by commit  https://github.com/dotnet/winforms/commit/a6a308fa452770caafca55ee4d8ac54d1ee1015d , reverting the changes in `OnPaint` can fixes this issue. 

<img width="1550" height="1179" alt="Image" src="https://github.com/user-attachments/assets/2f5a6de0-566d-4fd0-b276-a43e2e649be8" />


@JeremyKuhne Please confirm whether it is reasonable to revert this change here?
@JeremyKuhne it looks like something you might want to take a look at. Thanks @LeafShi1 for the investigation!
@LeafShi1 please see my PR. @koszeggy you should be able to work around this by getting and releasing the HDC from the paint event Graphics object when the form is painting its background.
@LeafShi1, I am starting to ramp up on this beginning (my) tomorrow morning.
But I'd like to know: Did this ever work in .NET 5,6 7 or 8? 
Do we know this?

(Also @Olina-Zhang, @merriemcgaw FYI. Short remark: Please do not spent much time to find this out, if you do not already know it. It is not _that_ important.)
@LeafShi1, @Olina-Zhang - never mind.
I looked briefly at https://github.com/dotnet/winforms/pull/3553. It was merged I think in the .NET 5 RC timeframe, so I figure 3.1 didn't have this issue, but we had it from 5.0 on.
It seems I was wrong with the versions and it worked in .NET Core 3.0 and 3.1. I fixed the opening post. The first non-working version was .NET 5.0
Verified the issue with .NET SDK 10.0.100-rc.1.25414.103 build, the issue has been fixed as below screenshot.
<img width="457" height="270" alt="Image" src="https://github.com/user-attachments/assets/f609cae6-e606-42c5-8730-b242e008957e" />
Verified it in .NET 10.0 RC1 SDK test pass build: 10.0.0-rc.1.25416.112, fixed with above same result.



Patch:
diff --git a/src/System.Private.Windows.Core/src/NativeMethods.txt b/src/System.Private.Windows.Core/src/NativeMethods.txt
index 56722d01258..87aeebe5d0c 100644
--- a/src/System.Private.Windows.Core/src/NativeMethods.txt
+++ b/src/System.Private.Windows.Core/src/NativeMethods.txt
@@ -118,10 +118,12 @@ GetSystemMetrics
 GetThreadLocale
 GetViewportExtEx
 GetViewportOrgEx
+GetWindowOrgEx
 GetWindowRect
 GetWindowText
 GetWindowTextLength
 GetWindowThreadProcessId
+GetWorldTransform
 GlobalAlloc
 GlobalFree
 GlobalLock
diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs
index 6c5b27b507d..819e03a250e 100644
--- a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs
+++ b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs
@@ -1,10 +1,31 @@
 ﻿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Drawing;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
 namespace Windows.Win32.Graphics.Gdi;
 
 internal readonly partial struct HDC : IHandle<HDC>
 {
     HDC IHandle<HDC>.Handle => this;
     object? IHandle<HDC>.Wrapper => null;
+
+    internal Point GetViewPointOrigin() =>
+        PInvokeCore.GetViewportOrgEx(this, out Point point) ? point : Point.Empty;
+
+    internal Point GetWindowOrigin() =>
+        PInvokeCore.GetWindowOrgEx(this, out Point point) ? point : Point.Empty;
+
+    internal Matrix3x2 GetWorldTransform()
+    {
+        if (PInvokeCore.GetWorldTransform(this, out XFORM matrix))
+        {
+            return Unsafe.As<XFORM, Matrix3x2>(ref matrix);
+        }
+
+        // If we can't get the transform, return the identity matrix.
+        return Matrix3x2.Identity;
+    }
 }
diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt
index dec3b0588de..180585d643a 100644
--- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt
+++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt
@@ -224,7 +224,6 @@ GetWindow
 GetWindowDisplayAffinity
 GetWindowDpiAwarenessContext
 GetWindowPlacement
-GetWorldTransform
 GMR_*
 HC_*
 HDHITTESTINFO
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs
index 2c9c7bfbdae..925ac0abd43 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs
@@ -462,6 +462,13 @@ internal static void DrawBackgroundImage(
             }
 
             g.FillRectangle(textureBrush, clipRect);
+
+            // If the Graphics backing HDC has an offset origin (SetViewportOrgEx), drawing with a texture brush will
+            // reset it. Getting the HDC and releasing it will restore the offset.
+            //
+            // See https://github.com/dotnet/winforms/issues/13784 for a repro.
+            g.GetHdc();
+            g.ReleaseHdc();
         }
         else
         {
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs
index f7912c6bcba..20e75dfadd0 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs
@@ -111,7 +111,7 @@ private static unsafe void ValidateHdc(HDC hdc)
         Debug.Assert(PInvoke.GetBkMode(hdc) == BACKGROUND_MODE.OPAQUE);
 
         Matrix3x2 matrix = default;
-        Debug.Assert(PInvoke.GetWorldTransform(hdc, (XFORM*)(void*)&matrix));
+        Debug.Assert(PInvokeCore.GetWorldTransform(hdc, (XFORM*)(void*)&matrix));
         Debug.Assert(matrix.IsIdentity);
     }
 }


Test patch:
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs
index 160c6d891a6..93e22893e85 100644
--- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs
@@ -32,9 +32,7 @@ public DeviceContextState(HDC hdc)
         TextAlign = PInvoke.GetTextAlign(hdc);
         BackgroundMode = PInvoke.GetBkMode(hdc);
 
-        Matrix3x2 transform = default;
-        PInvoke.GetWorldTransform(hdc, (XFORM*)(void*)&transform);
-        Transform = transform;
+        Transform = hdc.GetWorldTransform();
 
         Point point = default;
         PInvoke.GetBrushOrgEx(hdc, &point);