-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix issue 14795: ComboBox with DropDownStyle.Simple renders incorrectly and displays an unexpected vertical scrollbar #14827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -165,19 +165,6 @@ private ModernComboTargetState ComputeModernComboTargetState() | |
| { | ||
| int topInset = chromeInsets.Top + Padding.Top; | ||
| int bottomInset = chromeInsets.Bottom + Padding.Bottom; | ||
| editBounds.Y += topInset; | ||
|
|
||
| editBounds.Height = Math.Max( | ||
| 1, | ||
| editBounds.Height - topInset - bottomInset); | ||
|
|
||
| editBounds.Height = Math.Max( | ||
| 1, | ||
| Math.Min( | ||
| editBounds.Height, | ||
| ClientRectangle.Bottom | ||
| - bottomInset | ||
| - editBounds.Top)); | ||
|
|
||
| // Inset the edit window horizontally so its rectangular corners clear the rounded | ||
| // field arcs, and reserve the (now wider) drop-down button on the button side. | ||
|
|
@@ -189,30 +176,91 @@ private ModernComboTargetState ComputeModernComboTargetState() | |
| ModernControlVisualStyles.ComboBoxButtonExtraWidth, | ||
| DeviceDpiInternal); | ||
|
|
||
| if (RightToLeft == RightToLeft.Yes) | ||
| if (DropDownStyle == ComboBoxStyle.Simple) | ||
| { | ||
| // In RTL the drop-down button sits on the left, so its reservation goes there. | ||
| editBounds.X += rightInset + extraButtonWidth; | ||
| int dividerThickness = Math.Max( | ||
| 1, | ||
| ScaleHelper.ScaleToDpi( | ||
| ModernControlVisualStyles.BorderThickness, | ||
| DeviceDpiInternal)); | ||
| const int bottomCropLogicalPixels = 2; | ||
| int simpleBottomShrink = ScaleHelper.ScaleToDpi(bottomCropLogicalPixels, DeviceDpiInternal); | ||
| int simpleListBottom = ClientRectangle.Bottom - simpleBottomShrink - dividerThickness; | ||
| int simpleEditTop = topInset + ScaleHelper.ScaleToDpi(1, DeviceDpiInternal); | ||
| int selectionFieldHeight = Math.Max( | ||
| 1, | ||
| (int)PInvokeCore.SendMessage( | ||
| this, | ||
| PInvoke.CB_GETITEMHEIGHT, | ||
| (WPARAM)(-1))); | ||
| int minimumReadableHeight = Math.Max( | ||
| 1, | ||
| FontHeight + ScaleHelper.ScaleToDpi(2, DeviceDpiInternal)); | ||
| int preferredSimpleEditHeight = Math.Max(selectionFieldHeight, minimumReadableHeight) | ||
| + ScaleHelper.ScaleToDpi(2, DeviceDpiInternal); | ||
| int maxSimpleEditHeight = Math.Max( | ||
| 1, | ||
| simpleListBottom - dividerThickness - simpleEditTop); | ||
|
|
||
| // Keep the editor in the top field lane with enough height for full glyph rendering, | ||
| // draw an accent divider below it, and let the permanent list consume the remaining | ||
| // space down to the rounded field border. | ||
| editBounds.Y = simpleEditTop; | ||
| editBounds.Height = Math.Min(preferredSimpleEditHeight, maxSimpleEditHeight); | ||
| editBounds.X = leftInset; | ||
| editBounds.Width = Math.Max( | ||
| 1, | ||
| ClientRectangle.Width - leftInset - rightInset); | ||
|
|
||
| if (!simpleListBounds.IsEmpty) | ||
| { | ||
| simpleListBounds.X = editBounds.Left; | ||
| simpleListBounds.Width = editBounds.Width; | ||
| simpleListBounds.Y = editBounds.Bottom + dividerThickness; | ||
| simpleListBounds.Height = Math.Max( | ||
| 1, | ||
| simpleListBottom - simpleListBounds.Y); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| editBounds.X += leftInset; | ||
| } | ||
|
|
||
| editBounds.Width = Math.Max( | ||
| 1, | ||
| editBounds.Width - leftInset - rightInset - extraButtonWidth); | ||
|
|
||
| if (!simpleListBounds.IsEmpty) | ||
| { | ||
| int simpleListBottom = GetCurrentSimpleListBottom( | ||
| simpleListBounds.Bottom); | ||
| editBounds.Y += topInset; | ||
|
|
||
| simpleListBounds.Y = editBounds.Bottom + bottomInset; | ||
| editBounds.Height = Math.Max( | ||
| 1, | ||
| editBounds.Height - topInset - bottomInset); | ||
|
|
||
| simpleListBounds.Height = Math.Max( | ||
| editBounds.Height = Math.Max( | ||
| 1, | ||
| Math.Min( | ||
| editBounds.Height, | ||
| ClientRectangle.Bottom | ||
| - bottomInset | ||
| - editBounds.Top)); | ||
|
|
||
| if (RightToLeft == RightToLeft.Yes) | ||
| { | ||
| // In RTL the drop-down button sits on the left, so its reservation goes there. | ||
| editBounds.X += rightInset + extraButtonWidth; | ||
| } | ||
| else | ||
| { | ||
| editBounds.X += leftInset; | ||
| } | ||
|
|
||
| editBounds.Width = Math.Max( | ||
| 1, | ||
| simpleListBottom - simpleListBounds.Y); | ||
| editBounds.Width - leftInset - rightInset - extraButtonWidth); | ||
|
|
||
| if (!simpleListBounds.IsEmpty) | ||
| { | ||
| int simpleListBottom = GetCurrentSimpleListBottom( | ||
| simpleListBounds.Bottom); | ||
| simpleListBounds.Y = editBounds.Bottom + bottomInset; | ||
| simpleListBounds.Height = Math.Max( | ||
| 1, | ||
| simpleListBottom - simpleListBounds.Y); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -281,6 +329,60 @@ private void GetAdjustedNativeChildBounds( | |
| } | ||
| } | ||
|
|
||
| private void ConfigureModernSimpleListSurface(HWND listHandle) | ||
| { | ||
| if (DropDownStyle != ComboBoxStyle.Simple | ||
| || !UsesModernComboAdapter | ||
| || listHandle.IsNull) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| WINDOW_STYLE style = (WINDOW_STYLE)PInvokeCore.GetWindowLong( | ||
| listHandle, | ||
| WINDOW_LONG_PTR_INDEX.GWL_STYLE); | ||
| WINDOW_EX_STYLE exStyle = (WINDOW_EX_STYLE)PInvokeCore.GetWindowLong( | ||
| listHandle, | ||
| WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); | ||
|
|
||
| WINDOW_STYLE updatedStyle = style & ~WINDOW_STYLE.WS_BORDER; | ||
| WINDOW_EX_STYLE updatedExStyle = exStyle & ~WINDOW_EX_STYLE.WS_EX_CLIENTEDGE; | ||
|
|
||
| if (updatedStyle == style && updatedExStyle == exStyle) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (updatedStyle != style) | ||
| { | ||
| PInvokeCore.SetWindowLong( | ||
| listHandle, | ||
| WINDOW_LONG_PTR_INDEX.GWL_STYLE, | ||
| (nint)updatedStyle); | ||
| } | ||
|
|
||
| if (updatedExStyle != exStyle) | ||
| { | ||
| PInvokeCore.SetWindowLong( | ||
| listHandle, | ||
| WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, | ||
| (nint)updatedExStyle); | ||
| } | ||
|
|
||
| PInvoke.SetWindowPos( | ||
| listHandle, | ||
| HWND.Null, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| SET_WINDOW_POS_FLAGS.SWP_NOMOVE | ||
| | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | ||
| | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | ||
| | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | ||
| | SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED); | ||
| } | ||
|
|
||
| private unsafe int GetCurrentSimpleListBottom( | ||
| int fallback) | ||
| { | ||
|
|
@@ -464,24 +566,63 @@ private void ApplyEditMargins( | |
|
|
||
| private void ApplyChildBounds(HWND childHandle, Rectangle targetBounds) | ||
| { | ||
| bool isModernSimpleList = UsesModernComboAdapter | ||
| && DropDownStyle == ComboBoxStyle.Simple | ||
| && childHandle == _childListBox?.HWND; | ||
|
|
||
| if (childHandle.IsNull | ||
| || targetBounds.IsEmpty | ||
| || GetChildBounds(childHandle) == targetBounds) | ||
| || targetBounds.IsEmpty) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| PInvoke.SetWindowPos( | ||
| childHandle, | ||
| HWND.Null, | ||
| targetBounds.Left, | ||
| targetBounds.Top, | ||
| if (GetChildBounds(childHandle) != targetBounds) | ||
| { | ||
| PInvoke.SetWindowPos( | ||
| childHandle, | ||
| HWND.Null, | ||
| targetBounds.Left, | ||
| targetBounds.Top, | ||
| targetBounds.Width, | ||
| targetBounds.Height, | ||
| SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | ||
| | SET_WINDOW_POS_FLAGS.SWP_NOZORDER); | ||
|
|
||
| _modernComboLayoutWriteCount++; | ||
| } | ||
|
|
||
| if (isModernSimpleList) | ||
| { | ||
| ConfigureModernSimpleListClipRegion( | ||
| childHandle, | ||
| targetBounds); | ||
| } | ||
| } | ||
|
|
||
| private void ConfigureModernSimpleListClipRegion( | ||
| HWND listHandle, | ||
| Rectangle targetBounds) | ||
| { | ||
| if (listHandle.IsNull) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| int bottomShrink = ScaleHelper.ScaleToDpi(2, DeviceDpiInternal); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible double bottom-crop. The Simple layout already reserves |
||
| int visibleHeight = Math.Max(1, targetBounds.Height - bottomShrink); | ||
| using RegionScope region = new( | ||
| 0, | ||
| 0, | ||
| targetBounds.Width, | ||
| targetBounds.Height, | ||
| SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | ||
| | SET_WINDOW_POS_FLAGS.SWP_NOZORDER); | ||
| visibleHeight); | ||
|
|
||
| _modernComboLayoutWriteCount++; | ||
| if (PInvoke.SetWindowRgn( | ||
| listHandle, | ||
| region, | ||
| fRedraw: true) != 0) | ||
| { | ||
| region.RelinquishOwnership(); | ||
| } | ||
| } | ||
|
|
||
| private Rectangle GetChildBounds(HWND child) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -355,7 +355,9 @@ protected override CreateParams CreateParams | |
| cp.Style |= (int)WINDOW_STYLE.WS_VSCROLL | PInvoke.CBS_HASSTRINGS | PInvoke.CBS_AUTOHSCROLL; | ||
| cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_CLIENTEDGE; | ||
|
|
||
| if (!_integralHeight) | ||
| if (!_integralHeight | ||
| || (UsesModernComboAdapter | ||
| && DropDownStyle == ComboBoxStyle.Simple)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forcing |
||
| { | ||
| cp.Style |= PInvoke.CBS_NOINTEGRALHEIGHT; | ||
| } | ||
|
|
@@ -2408,6 +2410,12 @@ protected override unsafe void OnHandleCreated(EventArgs e) | |
| bool hasComboBoxInfo = PInvoke.GetComboBoxInfo( | ||
| HWND, | ||
| ref comboBoxInfo); | ||
|
|
||
| if (hasComboBoxInfo) | ||
| { | ||
| ConfigureModernSimpleListSurface(comboBoxInfo.hwndList); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| if (Application.IsDarkModeEnabled) | ||
| { | ||
| // Style the ComboBox Open-Button: | ||
|
|
@@ -3725,14 +3733,11 @@ protected override unsafe void WndProc(ref Message m) | |
| { | ||
| switch (m.MsgInternal) | ||
| { | ||
| // Modern VisualStyles: expand the client area to the full window so the drop-down | ||
| // button, which the themed ComboBox otherwise reserves as non-client (outside | ||
| // ClientRectangle), becomes part of the client and is covered by our rounded field. | ||
| // Simple combos have no drop-down button (their client already spans the full width | ||
| // and hosts a permanent list), so they must never be expanded. | ||
| // Modern VisualStyles: expand the client area to the full window so themed non-client | ||
| // reservations (for example the ComboBox-hosted scroll strip in Simple mode) are folded | ||
| // into client painting and can be normalized by modern layout. | ||
| case PInvokeCore.WM_NCCALCSIZE: | ||
| if (UsesModernComboAdapter | ||
| && DropDownStyle != ComboBoxStyle.Simple | ||
| && m.WParamInternal != 0u) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This now routes |
||
| { | ||
| RECT* ncRects = (RECT*)(nint)m.LParamInternal; | ||
|
|
@@ -3745,15 +3750,12 @@ protected override unsafe void WndProc(ref Message m) | |
| base.WndProc(ref m); | ||
| break; | ||
|
|
||
| // Modern VisualStyles: comctl32 still reports the drop-down button region as a | ||
| // non-client hit (HTVSCROLL) even though WM_NCCALCSIZE folded it into our client | ||
| // area, so a button click would arrive as WM_NCLBUTTONDOWN and never reach the | ||
| // WM_LBUTTONDOWN hit-test below. Force HTCLIENT across the expanded client so the | ||
| // button click is delivered as a normal client message we can act on. | ||
| // Modern VisualStyles: after WM_NCCALCSIZE expands the client area, comctl32 can still | ||
| // report the folded strip as non-client (HTVSCROLL). Force HTCLIENT across the expanded | ||
| // client so interaction remains consistent with the modern layout. | ||
| case PInvokeCore.WM_NCHITTEST: | ||
| base.WndProc(ref m); | ||
| if (UsesModernComboAdapter | ||
| && DropDownStyle != ComboBoxStyle.Simple | ||
| && m.ResultInternal != PInvoke.HTCLIENT) | ||
| { | ||
| Point hitPoint = PointToClient(PARAM.ToPoint(m.LParamInternal)); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The clip region is re-applied on every layout pass.
ConfigureModernSimpleListClipRegionruns unconditionally inApplyChildBoundsfor the modern Simple list, even when the target bounds are unchanged. While theSetWindowPoscall is guarded against no-op moves, thisSetWindowRgn(..., fRedraw: true)is not, so every relayout forces a region reset and repaint. Consider skipping the region update when the computed clip rect matches the previously applied one to avoid unnecessary work and potential flicker.