From 1f258060d34766c0c8e5383497b0f11068e0f186 Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Wed, 5 Aug 2026 10:54:38 +0800 Subject: [PATCH 1/3] Fix issue 14796: ComboBox with FlatStyle.System renders incorrectly at design time --- .../Controls/ComboBox/ComboBox.Modern.cs | 13 ++++- .../Forms/Controls/ComboBox/ComboBox.cs | 22 +++++-- .../System/Windows/Forms/ComboBoxTests.cs | 58 +++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs index 90eeab9368b..71f5eb25bdc 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -654,12 +654,23 @@ protected override void OnVisualStylesModeChanged(EventArgs e) ResetComboAdapter(); ResetHeightCache(); + bool recreateSystemHandle = FlatStyle == FlatStyle.System + && IsHandleCreated; + // Crossing the modern/classic boundary is reported as VisualStylesModeChangeImpact.Recreate // (see GetVisualStylesModeChangeImpact), so the base recreates the handle here. That is the // only clean way to unwind the modern native-window state (the WM_NCCALCSIZE client // expansion and the per-handle modern baseline); a fresh classic handle then behaves exactly // as before, and a fresh modern handle captures a clean baseline. + // FlatStyle.System stays native, but an existing native handle still needs a rebuild when + // visual-style mode changes to avoid stale geometry in designer/runtime transitions. base.OnVisualStylesModeChanged(e); + + if (recreateSystemHandle && IsHandleCreated) + { + RecreateHandle(); + } + ApplyModernComboLayout(); RefreshModernDropDownCornerPreference(); } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs index 0e2e5c6e0a7..9b55d538f61 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; @@ -534,11 +534,22 @@ public partial FlatStyle FlatStyle _flatStyle = value; ResetComboAdapter(); - if (usedModernMetrics != UsesModernComboAdapter) + bool usesModernMetrics = UsesModernComboAdapter; + bool modernMetricsChanged = usedModernMetrics != usesModernMetrics; + + if (modernMetricsChanged) { ResetHeightCache(); CommonProperties.xClearPreferredSizeCache(this); - ApplyModernComboLayout(); + + if (IsHandleCreated) + { + RecreateHandle(); + } + else + { + ApplyModernComboLayout(); + } LayoutTransaction.DoLayout( this, @@ -553,8 +564,11 @@ public partial FlatStyle FlatStyle PropertyNames.FlatStyle); } } + else + { + ApplyModernComboLayout(); + } - ApplyModernComboLayout(); RefreshModernDropDownCornerPreference(); Invalidate(); } diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComboBoxTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComboBoxTests.cs index 8ba593350fd..ce7d377beef 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComboBoxTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComboBoxTests.cs @@ -287,6 +287,64 @@ public void ComboBox_ModernVisualStyles_SystemModeChangeDoesNotRequestLayout() Assert.False(control.IsHandleCreated); } + [WinFormsFact] + public void ComboBox_ModernVisualStyles_FlatStyleTransitionToSystem_RecreatesHandle() + { + using SystemVisualSettingsTestScope settingsScope = new( + clientAreaAnimationEnabled: false, + highContrastEnabled: false); + using Panel parent = new(); + using VisualStylesComboBox control = new() + { + FlatStyle = FlatStyle.Standard, + VisualStylesMode = VisualStylesMode.Net11 + }; + parent.Controls.Add(control); + parent.CreateControl(); + control.CreateControl(); + + int handleCreatedCallCount = 0; + int handleDestroyedCallCount = 0; + control.HandleCreated += (sender, e) => handleCreatedCallCount++; + control.HandleDestroyed += (sender, e) => handleDestroyedCallCount++; + + control.FlatStyle = FlatStyle.System; + + Assert.True(control.IsHandleCreated); + Assert.Equal(1, handleDestroyedCallCount); + Assert.Equal(1, handleCreatedCallCount); + Assert.IsType(control.CreateAdapter()); + } + + [WinFormsFact] + public void ComboBox_ModernVisualStyles_SystemModeBoundaryChangeWithHandle_RecreatesHandle() + { + using SystemVisualSettingsTestScope settingsScope = new( + clientAreaAnimationEnabled: false, + highContrastEnabled: false); + using Panel parent = new(); + using VisualStylesComboBox control = new() + { + FlatStyle = FlatStyle.System, + VisualStylesMode = VisualStylesMode.Classic + }; + parent.Controls.Add(control); + parent.CreateControl(); + control.CreateControl(); + + int handleCreatedCallCount = 0; + int handleDestroyedCallCount = 0; + control.HandleCreated += (sender, e) => handleCreatedCallCount++; + control.HandleDestroyed += (sender, e) => handleDestroyedCallCount++; + + control.VisualStylesMode = VisualStylesMode.Net11; + + Assert.True(control.IsHandleCreated); + Assert.Equal(1, handleDestroyedCallCount); + Assert.Equal(1, handleCreatedCallCount); + Assert.IsType(control.CreateAdapter()); + } + [WinFormsFact] public void ComboBox_ModernVisualStyles_ModeChangeRemeasuresAutoSizeRow() { From 40904328a7cd9e8417840f0aaa2d0b14571b12e7 Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Wed, 5 Aug 2026 16:41:17 +0800 Subject: [PATCH 2/3] Handle feedback --- .../Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs | 2 +- .../System/Windows/Forms/Controls/ComboBox/ComboBox.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs index 71f5eb25bdc..766c40d952d 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.Modern.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs index 9b55d538f61..81f826265c9 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; @@ -530,12 +530,12 @@ public partial FlatStyle FlatStyle return; } - bool usedModernMetrics = UsesModernComboAdapter; + bool previousUsesModernMetrics = UsesModernComboAdapter; _flatStyle = value; ResetComboAdapter(); - bool usesModernMetrics = UsesModernComboAdapter; - bool modernMetricsChanged = usedModernMetrics != usesModernMetrics; + bool currentUsesModernMetrics = UsesModernComboAdapter; + bool modernMetricsChanged = previousUsesModernMetrics != currentUsesModernMetrics; if (modernMetricsChanged) { From 562fb6e570c3147c84e24e4f80521daf7213c3fc Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Thu, 6 Aug 2026 09:47:10 +0800 Subject: [PATCH 3/3] Handle feedback: Optimize the code --- .../System/Windows/Forms/Controls/ComboBox/ComboBox.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs index a5b0e787da1..fca5b5c33ef 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs @@ -535,9 +535,8 @@ public partial FlatStyle FlatStyle ResetComboAdapter(); bool currentUsesModernMetrics = UsesModernComboAdapter; - bool modernMetricsChanged = previousUsesModernMetrics != currentUsesModernMetrics; - if (modernMetricsChanged) + if (previousUsesModernMetrics != currentUsesModernMetrics) { ResetHeightCache(); CommonProperties.xClearPreferredSizeCache(this);