The drop-down boxes stop being Windows-coloured

CBS_OWNERDRAWFIXED only hands over the item area, so while the list rows
came out green on black, the closed box kept the system's frame and drop
arrow - a white/grey Windows control sitting in the middle of a black panel.

The closed box is painted here now: black field, dim green border, bright
green text, and a plain green triangle instead of a themed button. The
control keeps doing everything else, including dropping its list, so this is
a subclass over WM_PAINT rather than a reimplementation.

Still system-drawn: the scrollbar inside a dropped list, which only appears
on the two lists longer than twelve rows - vehicle and track.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-07 09:55:10 -05:00
co-authored by Claude Opus 5
parent 44f5a2c6fd
commit b3ed7bc141
+87
View File
@@ -652,6 +652,83 @@ namespace
// The combo id is its group, so CBN_SELCHANGE says which list moved.
const int kComboIdBase = 100;
//---------------------------------------------------------------
// CBS_OWNERDRAWFIXED only hands us the item area: the frame around
// the closed box and its drop arrow are still drawn by the system,
// which lands a white/grey Windows control in the middle of a black
// panel. So the closed box is painted here instead - black field,
// dim green border, bright green text and arrow - and the control
// keeps doing everything else, including the list it drops.
//---------------------------------------------------------------
WNDPROC gComboProc = NULL;
LRESULT CALLBACK ComboSubclassProc(
HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_ERASEBKGND)
{
return 1;
}
if (message == WM_PAINT)
{
PAINTSTRUCT ps;
HDC dc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
HBRUSH field = CreateSolidBrush(kBlack);
FillRect(dc, &rc, field);
DeleteObject(field);
HBRUSH edge = CreateSolidBrush(kGreenDim);
FrameRect(dc, &rc, edge);
DeleteObject(edge);
int arrow_w = rc.bottom - rc.top;
char text[128];
text[0] = '\0';
int sel = (int) SendMessageA(hwnd, CB_GETCURSEL, 0, 0);
if (sel >= 0)
{
SendMessageA(hwnd, CB_GETLBTEXT, sel, (LPARAM) text);
}
HFONT font = (HFONT) SendMessageA(hwnd, WM_GETFONT, 0, 0);
HGDIOBJ old_font = (font != NULL) ? SelectObject(dc, font) : NULL;
SetBkMode(dc, TRANSPARENT);
SetTextColor(dc, kGreenBright);
RECT label = rc;
label.left += 6;
label.right -= arrow_w;
DrawTextA(dc, text, -1, &label,
DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
// the arrow, a plain triangle rather than a themed button
int cx = rc.right - arrow_w / 2;
int cy = (rc.top + rc.bottom) / 2;
int r = arrow_w / 6;
POINT tri[3];
tri[0].x = cx - r; tri[0].y = cy - r / 2;
tri[1].x = cx + r; tri[1].y = cy - r / 2;
tri[2].x = cx; tri[2].y = cy + r;
HBRUSH tip = CreateSolidBrush(kGreenBright);
HGDIOBJ old_brush = SelectObject(dc, tip);
HGDIOBJ old_pen = SelectObject(dc, GetStockObject(NULL_PEN));
Polygon(dc, tri, 3);
SelectObject(dc, old_pen);
SelectObject(dc, old_brush);
DeleteObject(tip);
if (old_font != NULL) SelectObject(dc, old_font);
EndPaint(hwnd, &ps);
return 0;
}
return CallWindowProcA(gComboProc, hwnd, message, wParam, lParam);
}
void DestroyCombos(FEState *fe)
{
for (int g = 0; g < GroupCount; ++g)
@@ -827,6 +904,14 @@ namespace
SendMessageA(box, WM_SETFONT, (WPARAM) fe->textFont, TRUE);
SendMessageA(box, CB_SETITEMHEIGHT, (WPARAM) -1, row_h);
SendMessageA(box, CB_SETITEMHEIGHT, 0, row_h);
// paint the closed box ourselves - see ComboSubclassProc
WNDPROC previous = (WNDPROC) SetWindowLongPtrA(
box, GWLP_WNDPROC, (LONG_PTR) ComboSubclassProc);
if (gComboProc == NULL)
{
gComboProc = previous;
}
for (int i = 0; i < count; ++i)
{
SendMessageA(box, CB_ADDSTRING, 0, (LPARAM) ItemName(group, i));
@@ -1104,6 +1189,8 @@ namespace
{
fe->selection[group] = pick;
}
// the closed box is ours to redraw now
InvalidateRect(fe->combo[group], NULL, FALSE);
InvalidateRect(fe->menuWindow, NULL, FALSE);
return 0;
}