Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public partial class ComboBox
private bool _nativeComboHandleInitialized;
private bool _normalizingNativeComboBaseline;
private int _modernComboLayoutWriteCount;
private HWND _modernSimpleListClipRegionHandle;
private Size _modernSimpleListClipRegionSize;
private int _modernSimpleListClipRegionApplyCount;
private const int ModernSimpleBottomCropLogicalPixels = 2;

internal bool UsesModernComboAdapter
=> EffectiveVisualStylesMode >= VisualStylesMode.Net11
Expand Down Expand Up @@ -165,19 +169,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.
Expand All @@ -189,30 +180,88 @@ 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 = GetModernSimpleDividerThickness();
int simpleBottomShrink = ScaleHelper.ScaleToDpi(
ModernSimpleBottomCropLogicalPixels,
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);
editBounds.Y += topInset;

if (!simpleListBounds.IsEmpty)
{
int simpleListBottom = GetCurrentSimpleListBottom(
simpleListBounds.Bottom);

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);
}
}
}

Expand Down Expand Up @@ -281,6 +330,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)
{
Expand Down Expand Up @@ -366,6 +469,8 @@ private unsafe void ApplyModernComboLayout()
return;
}

ConfigureModernSimpleListSurface(comboBoxInfo.hwndList);

if (!target.EditBounds.IsEmpty)
{
ApplyEditMargins(
Expand Down Expand Up @@ -464,26 +569,85 @@ 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,
targetBounds.Width,
targetBounds.Height,
SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE
| SET_WINDOW_POS_FLAGS.SWP_NOZORDER);
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++;
_modernComboLayoutWriteCount++;
}

if (isModernSimpleList)
{
ConfigureModernSimpleListClipRegion(
Comment thread
SimonZhao888 marked this conversation as resolved.
childHandle,
targetBounds);
}
}

private void ConfigureModernSimpleListClipRegion(
HWND listHandle,
Rectangle targetBounds)
{
if (listHandle.IsNull)
{
return;
}

int bottomShrink = ScaleHelper.ScaleToDpi(
ModernSimpleBottomCropLogicalPixels,
DeviceDpiInternal);
int visibleHeight = Math.Max(1, targetBounds.Height - bottomShrink);
Size targetRegionSize = new(targetBounds.Width, visibleHeight);

if (listHandle == _modernSimpleListClipRegionHandle
&& targetRegionSize == _modernSimpleListClipRegionSize)
{
return;
}

using RegionScope region = new(
0,
0,
targetRegionSize.Width,
targetRegionSize.Height);

if (PInvoke.SetWindowRgn(
listHandle,
region,
fRedraw: true) != 0)
{
region.RelinquishOwnership();
_modernSimpleListClipRegionHandle = listHandle;
_modernSimpleListClipRegionSize = targetRegionSize;
_modernSimpleListClipRegionApplyCount++;
}
}

private int GetModernSimpleDividerThickness()
=> Math.Max(
1,
ScaleHelper.ScaleToDpi(
ModernControlVisualStyles.BorderThickness,
DeviceDpiInternal));

private Rectangle GetChildBounds(HWND child)
{
PInvokeCore.GetWindowRect(child, out RECT bounds);
Expand Down Expand Up @@ -526,6 +690,9 @@ private int GetNativeComboBaselineSelectionFieldItemHeight()
private int GetModernComboLayoutWriteCount()
=> _modernComboLayoutWriteCount;

private int GetModernSimpleListClipRegionApplyCount()
=> _modernSimpleListClipRegionApplyCount;

private Padding GetModernFieldPadding()
{
SystemVisualSettings settings = Application.SystemVisualSettings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public override void DrawFlatCombo(
graphics,
clientBounds);
DrawDropDownButton(comboBox, graphics);
DrawSimpleEditListDivider(comboBox, graphics);

switch (_flatStyle)
{
Expand Down Expand Up @@ -395,6 +396,45 @@ private static void CutOutRoundedCorners(
parentColor);
}

private static void DrawSimpleEditListDivider(
ComboBox comboBox,
Graphics graphics)
{
if (comboBox.DropDownStyle != ComboBoxStyle.Simple
|| comboBox._childListBox is null)
{
return;
}

Rectangle listBounds = comboBox.GetChildBounds(comboBox._childListBox.HWND);
if (listBounds.IsEmpty)
{
return;
}

int dividerThickness = Math.Max(
1,
ScaleHelper.ScaleToDpi(
ModernControlVisualStyles.BorderThickness,
comboBox.DeviceDpiInternal));
int lineY = listBounds.Top - dividerThickness;
if (lineY < 0)
{
return;
}

Padding chromeInsets = comboBox.GetModernChromeInsets();
int left = chromeInsets.Left + comboBox.Padding.Left;
int right = comboBox.ClientRectangle.Right - chromeInsets.Right - comboBox.Padding.Right - 1;
if (right < left)
{
return;
}

using var pen = Application.SystemVisualSettings.AccentColor.GetCachedPenScope(dividerThickness);
graphics.DrawLine(pen, left, lineY, right, lineY);
}

private static void DrawRoundedBorder(
ComboBox comboBox,
Graphics graphics,
Expand Down
Loading