diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.ComboBoxChildNativeWindow.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.ComboBoxChildNativeWindow.cs index 60b17e60db7..0ff802ec8e9 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.ComboBoxChildNativeWindow.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.ComboBoxChildNativeWindow.cs @@ -25,6 +25,18 @@ protected override unsafe void WndProc(ref Message m) case PInvokeCore.WM_GETOBJECT: WmGetObject(ref m); return; + case PInvokeCore.WM_WINDOWPOSCHANGING: + if (_childWindowType == ChildWindowType.DropDownList) + { + WmWindowPosChanging(ref m); + DefWndProc(ref m); + } + else + { + _owner.ChildWndProc(ref m); + } + + break; case PInvokeCore.WM_MOUSEMOVE: if (_childWindowType == ChildWindowType.DropDownList) { @@ -153,5 +165,23 @@ private unsafe void WmGetObject(ref Message m) throw new InvalidOperationException(SR.RichControlLresult, e); } } + + private unsafe void WmWindowPosChanging(ref Message m) + { + // The native ComboBox sizes the dropdown list during its own layout + // pass (after CBN_DROPDOWN fires). Intercept here to enforce the + // managed computed height before the OS commits the final size. + // This ensures the UI reflects Items.Count (or explicit DropDownHeight) + // even when the list is empty or items are cleared at runtime. + WINDOWPOS* pos = (WINDOWPOS*)(nint)m.LParamInternal; + if (pos is not null && (pos->flags & SET_WINDOW_POS_FLAGS.SWP_NOSIZE) == 0) + { + int height = _owner.GetCalculatedDropDownHeight(); + if (pos->cy != height) + { + pos->cy = height; + } + } + } } } 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 5e4422eaf9c..86e36af873c 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 @@ -3327,14 +3327,8 @@ public override string ToString() return $"{s}, Items.Count: {_itemsCollection?.Count ?? 0}"; } - private void UpdateDropDownHeight() + private int GetCalculatedDropDownHeight() { - if (_dropDownHandle.IsNull) - { - return; - } - - // Now use the DropDownHeight property instead of calculating the Height... int height = DropDownHeight; if (height == DefaultDropDownHeight) { @@ -3343,6 +3337,19 @@ private void UpdateDropDownHeight() height = ItemHeight * count + 2; } + return height; + } + + private void UpdateDropDownHeight() + { + if (_dropDownHandle.IsNull) + { + return; + } + + // Now use the DropDownHeight property instead of calculating the Height... + int height = GetCalculatedDropDownHeight(); + PInvoke.SetWindowPos( _dropDownHandle, HWND.HWND_TOP, 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 bcb46fd6318..fc4d635b33b 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 @@ -2742,6 +2742,60 @@ public void ComboBox_CorrectHeightAfterSetDropDownStyleSimple() handleCreatedInvoked.Should().Be(2); } + [WinFormsFact] + public void ComboBox_GetCalculatedDropDownHeight_Reflects_Items_Clear() + { + using ComboBox combo = new(); + combo.DropDownStyle = ComboBoxStyle.DropDown; + + // Add items to influence calculated height and create handle. + for (int i = 0; i < 20; i++) + { + combo.Items.Add($"item{i}"); + } + + Assert.NotEqual(IntPtr.Zero, combo.Handle); + + MethodInfo mi = typeof(ComboBox).GetMethod("GetCalculatedDropDownHeight", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(mi); + + int heightWithItems = (int)mi.Invoke(combo, null); + + combo.Items.Clear(); + + mi = typeof(ComboBox).GetMethod("GetCalculatedDropDownHeight", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(mi); + int heightAfterClear = (int)mi.Invoke(combo, null); + + // After clearing items the calculated height should change and not remain the previous items height. + Assert.NotEqual(heightWithItems, heightAfterClear); + } + + [WinFormsFact] + public void ComboBox_GetCalculatedDropDownHeight_Uses_DropDownHeight_When_NoItems() + { + using ComboBox combo = new(); + combo.DropDownStyle = ComboBoxStyle.DropDown; + + // Add items to influence calculated height and create handle. + for (int i = 0; i < 2; i++) + { + combo.Items.Add($"item{i}"); + } + + // Ensure handle exists and items are empty. + Assert.NotEqual(IntPtr.Zero, combo.Handle); + combo.Items.Clear(); + + MethodInfo mi = typeof(ComboBox).GetMethod("GetCalculatedDropDownHeight", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(mi); + + int calculated = (int)mi.Invoke(combo, null); + + // Calculated height should not exceed the explicit DropDownHeight when there are no items. + Assert.InRange(calculated, 0, combo.DropDownHeight); + } + private void InitializeItems(ComboBox comboBox, int numItems) { for (int i = 0; i < numItems; i++)