Skip to content

Commit 62f001b

Browse files
[release/10.0] Fix Region update logic to ensure Region updates are applied correctly when handle is created (#14035)
* Fix Region update logic to prevent loss of custom shapes
1 parent cfb7ae4 commit 62f001b

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-1
lines changed

src/System.Windows.Forms.Primitives/src/NativeMethods.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ GetWindow
224224
GetWindowDisplayAffinity
225225
GetWindowDpiAwarenessContext
226226
GetWindowPlacement
227+
GetWindowRgn
227228
GMR_*
228229
HC_*
229230
HDHITTESTINFO

src/System.Windows.Forms/System/Windows/Forms/Control.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2823,7 +2823,7 @@ public Region? Region
28232823
{
28242824
Region? oldRegion = Properties.AddOrRemoveValue(s_regionProperty, region);
28252825

2826-
if (oldRegion == region || !IsHandleCreated)
2826+
if (!IsHandleCreated)
28272827
{
28282828
// We'll get called when OnHandleCreated runs.
28292829
return oldRegion;

src/test/unit/System.Windows.Forms/System.Windows.Forms.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
<ItemGroup>
3030
<ProjectReference Include="..\..\..\BuildAssist\BuildAssist.msbuildproj" />
3131
<ProjectReference Include="..\..\..\System.Private.Windows.Core\tests\System.Private.Windows.Core.Tests\System.Private.Windows.Core.Tests.csproj" />
32+
<ProjectReference Include="..\..\..\System.Private.Windows.GdiPlus\System.Private.Windows.GdiPlus.csproj" />
33+
<ProjectReference Include="..\..\..\System.Windows.Forms.Primitives\src\System.Windows.Forms.Primitives.csproj" />
3234
<ProjectReference Include="..\..\..\System.Windows.Forms\System.Windows.Forms.csproj" />
3335
<ProjectReference Include="..\..\..\System.Design\src\System.Design.Facade.csproj" />
3436
<!-- This facade assembly is required to resolve UITypeEditor type from NETFX that is referenced in EditorAttributes -->

src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System.ComponentModel;
77
using System.Drawing;
8+
using Windows.Win32.Graphics.GdiPlus;
89
using Point = System.Drawing.Point;
910
using Size = System.Drawing.Size;
1011

@@ -1109,6 +1110,97 @@ public void Control_OnCreateControl_Triggered_Once_Expected()
11091110
Assert.Equal(1, control.OnCreateControlCount);
11101111
}
11111112

1113+
[WinFormsFact]
1114+
public void Control_Region_ReassignSameReference_DoesNotThrow()
1115+
{
1116+
using TestControl control = new();
1117+
control.CreateControl();
1118+
1119+
using Region region = new(new Rectangle(0, 0, 50, 30));
1120+
control.Region = region;
1121+
1122+
Action action = () => control.Region = region;
1123+
action.Should().NotThrow();
1124+
control.Region.Should().BeSameAs(region);
1125+
}
1126+
1127+
[WinFormsFact]
1128+
public void Control_Region_ReassignDifferentReference_UpdatesRegion()
1129+
{
1130+
using TestControl control = new();
1131+
control.CreateControl();
1132+
1133+
using Region region1 = new(new Rectangle(0, 0, 50, 30));
1134+
control.Region = region1;
1135+
1136+
using Region region2 = new(new Rectangle(10, 10, 60, 40));
1137+
control.Region = region2;
1138+
1139+
control.Region.Should().BeSameAs(region2);
1140+
}
1141+
1142+
[WinFormsFact]
1143+
public void Control_Region_ShouldPersistAfterHandleCreated()
1144+
{
1145+
using TestControl control = new() { Size = new Size(200, 100) };
1146+
1147+
// Create expected Region (rectangle)
1148+
Rectangle expectedRect = new Rectangle(0, 0, control.Width, control.Height);
1149+
using Region expectedRegion = new Region(expectedRect);
1150+
1151+
// Inspect expected HRGN rectangles
1152+
using RegionScope scope = expectedRegion.GetRegionScope(HWND.Null);
1153+
HRGN hrgnExpected = scope.Region;
1154+
var expectedRects = hrgnExpected.GetRegionRects();
1155+
1156+
// Assert expectedRects contains exactly one rectangle matching expectedRect
1157+
expectedRects.Should().HaveCount(1);
1158+
expectedRects[0].left.Should().Be(expectedRect.Left);
1159+
expectedRects[0].top.Should().Be(expectedRect.Top);
1160+
expectedRects[0].right.Should().Be(expectedRect.Right);
1161+
expectedRects[0].bottom.Should().Be(expectedRect.Bottom);
1162+
1163+
// Assign Region to control
1164+
control.Region = expectedRegion;
1165+
1166+
// Create handle
1167+
control.CreateControl();
1168+
control.IsHandleCreated.Should().BeTrue();
1169+
1170+
// Validate actual HRGN from the control's window
1171+
HRGN hrgnActual = PInvoke.CreateRectRgn(0, 0, 0, 0);
1172+
try
1173+
{
1174+
int result = (int)PInvoke.GetWindowRgn((HWND)control.Handle, hrgnActual);
1175+
1176+
// Ensure region exists
1177+
result.Should().BeGreaterThan(1);
1178+
1179+
var actualRects = hrgnActual.GetRegionRects();
1180+
1181+
// Assert actualRects matches expected rectangle
1182+
actualRects.Should().HaveCount(1);
1183+
actualRects[0].left.Should().Be(expectedRect.Left);
1184+
actualRects[0].top.Should().Be(expectedRect.Top);
1185+
actualRects[0].right.Should().Be(expectedRect.Right);
1186+
actualRects[0].bottom.Should().Be(expectedRect.Bottom);
1187+
}
1188+
finally
1189+
{
1190+
PInvokeCore.DeleteObject(hrgnActual);
1191+
}
1192+
}
1193+
1194+
[WinFormsFact]
1195+
public void Control_Region_AssignNull_ShouldBeNull()
1196+
{
1197+
using TestControl control = new();
1198+
control.CreateControl();
1199+
1200+
control.Region = null;
1201+
control.Region.Should().BeNull();
1202+
}
1203+
11121204
private class OnCreateControlCounter : Control
11131205
{
11141206
public int OnCreateControlCount { get; set; }

0 commit comments

Comments
 (0)