Comctl32 v5 listview sets the initial background mix mode to TRANSPARENT. Comctl32 v6 listview sets it to OPAQUE. Use TRANSPARENT for both v5 and v6 for now.
-- v2: comctl32/listview: Set the initial background mix mode to TRANSPARENT. comctl32/tests: Test listview background mix mode.
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/comctl32/tests/listview.c | 210 +++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+)
diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c index b767106e97a..8eb04ba55ef 100644 --- a/dlls/comctl32/tests/listview.c +++ b/dlls/comctl32/tests/listview.c @@ -2202,6 +2202,214 @@ static void test_customdraw(void) DestroyWindow(hwnd); }
+static LRESULT WINAPI customdraw_background_wndproc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp) +{ + if (message == WM_NOTIFY) + { + NMHDR *nmhdr = (NMHDR *)lp; + + if (nmhdr->code == NM_CUSTOMDRAW) + { + NMLVCUSTOMDRAW *nmlvcd = (NMLVCUSTOMDRAW *)nmhdr; + struct message msg; + + msg.message = message; + msg.flags = sent | wparam | lparam | id | custdraw; + msg.wParam = wp; + msg.lParam = lp; + msg.id = GetBkMode(nmlvcd->nmcd.hdc); + msg.stage = nmlvcd->nmcd.dwDrawStage; + add_message(sequences, PARENT_CD_SEQ_INDEX, &msg); + + switch (nmlvcd->nmcd.dwDrawStage) + { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT; + case CDDS_ITEMPREPAINT: + return CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT; + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: + return CDRF_NOTIFYPOSTPAINT; + case CDDS_ITEMPOSTPAINT | CDDS_SUBITEM: + return CDRF_NOTIFYPOSTPAINT; + case CDDS_POSTPAINT: + return CDRF_DODEFAULT; + } + return CDRF_DODEFAULT; + } + } + + return DefWindowProcA(hwnd, message, wp, lp); +} + +static const struct message parent_list_cd_bk_seq[] = +{ + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_POSTPAINT}, + {0} +}; + +static const struct message v5_parent_report_cd_bk_seq[] = +{ + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_POSTPAINT}, + {0} +}; + +static const struct message v6_64bit_parent_report_cd_bk_seq[] = +{ + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_POSTPAINT}, + {0} +}; + +static const struct message v6_32bit_parent_report_cd_bk_seq[] = +{ + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_POSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT | CDDS_SUBITEM}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_POSTPAINT}, + {0} +}; + +static const struct message v6_64bit_ownerdrawfixed_parent_list_cd_bk_seq[] = +{ + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_POSTPAINT}, + {0} +}; + +static const struct message v6_32bit_ownerdrawfixed_parent_list_cd_bk_seq[] = +{ + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, OPAQUE, CDDS_POSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_PREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPREPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_ITEMPOSTPAINT}, + {WM_NOTIFY, sent | id | custdraw, 0, 0, TRANSPARENT, CDDS_POSTPAINT}, + {0} +}; + +static void test_customdraw_background(BOOL v6) +{ + static const DWORD test_styles[] = {LVS_ICON, LVS_REPORT, LVS_SMALLICON, LVS_LIST, LVS_REPORT | LVS_OWNERDRAWFIXED}; + static const COLORREF test_colors[] = {CLR_DEFAULT, CLR_NONE, RGB(255, 0, 0)}; + BOOL is_64bit = sizeof(void *) == 8; + const struct message *expected_msgs; + WNDPROC oldwndproc; + unsigned int i, j; + LVITEMA item; + HWND hwnd; + + for (i = 0; i < ARRAY_SIZE(test_styles); i++) + { + for (j = 0; j < ARRAY_SIZE(test_colors); j++) + { + winetest_push_context("%s %dbit style %#lx color %#lx", v6 ? "v6" : "v5", + is_64bit ? 64 : 32, test_styles[i], test_colors[j]); + + if (v6) + { + if (test_styles[i] == LVS_REPORT) + { + if (is_64bit) + expected_msgs = v6_64bit_parent_report_cd_bk_seq; + else + expected_msgs = v6_32bit_parent_report_cd_bk_seq; + } + else if (test_styles[i] == (LVS_REPORT | LVS_OWNERDRAWFIXED)) + { + if (is_64bit) + expected_msgs = v6_64bit_ownerdrawfixed_parent_list_cd_bk_seq; + else + expected_msgs = v6_32bit_ownerdrawfixed_parent_list_cd_bk_seq; + } + else + { + expected_msgs = parent_list_cd_bk_seq; + } + } + else + { + if (test_styles[i] == LVS_REPORT) + expected_msgs = v5_parent_report_cd_bk_seq; + else + expected_msgs = parent_list_cd_bk_seq; + } + + hwnd = create_listview_control(test_styles[i]); + SendMessageA(hwnd, LVM_SETTEXTBKCOLOR, 0, test_colors[j]); + + insert_column(hwnd, 0); + insert_column(hwnd, 1); + insert_item(hwnd, 0); + flush_events(); + + oldwndproc = (WNDPROC)SetWindowLongPtrA(hwndparent, GWLP_WNDPROC, (LONG_PTR)customdraw_background_wndproc); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + InvalidateRect(hwnd, NULL, TRUE); + UpdateWindow(hwnd); + ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "Normal", TRUE); + + /* LVIS_SELECTED */ + item.mask = LVIF_STATE; + item.stateMask = LVIS_SELECTED; + item.state = LVIS_SELECTED; + SendMessageA(hwnd, LVM_SETITEMSTATE, 0, (LPARAM)&item); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + InvalidateRect(hwnd, NULL, TRUE); + UpdateWindow(hwnd); + ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "LVIS_SELECTED", TRUE); + + /* LVS_SHOWSELALWAYS */ + SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) | LVS_SHOWSELALWAYS); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + InvalidateRect(hwnd, NULL, TRUE); + UpdateWindow(hwnd); + ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "LVS_SHOWSELALWAYS", TRUE); + + SetWindowLongPtrA(hwndparent, GWLP_WNDPROC, (LONG_PTR)oldwndproc); + flush_sequences(sequences, NUM_MSG_SEQUENCES); + DestroyWindow(hwnd); + winetest_pop_context(); + } + } +} + static void test_icon_spacing(void) { /* LVM_SETICONSPACING */ @@ -7511,6 +7719,7 @@ START_TEST(listview) test_create(FALSE); test_redraw(); test_customdraw(); + test_customdraw_background(FALSE); test_icon_spacing(); test_color(); test_item_count(); @@ -7611,6 +7820,7 @@ START_TEST(listview) test_LVM_SETBKIMAGE(TRUE); test_LVM_GETHOTCURSOR(); test_LVM_GETORIGIN(TRUE); + test_customdraw_background(TRUE);
uninit_winevent_hook();
From: Zhiyi Zhang zzhang@codeweavers.com
Comctl32 v5 listview sends custom draw messages with TRANSPARENT background mix mode. Comctl32 v6 listview sends custom draw messages with OPAQUE first to draw headers, then send the another round of custom draw messages sequence with TRANSPARENT to draw items. Wine's comctl32 currently doesn't draw headers separately. So it seems reasonable to set TRANSPARENT for the complete custom draw message sequence on Wine for now. --- dlls/comctl32/listview.c | 9 ++------- dlls/comctl32/tests/listview.c | 9 ++++++--- 2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c index 31f8a933e56..486ec074cd1 100644 --- a/dlls/comctl32/listview.c +++ b/dlls/comctl32/listview.c @@ -1120,13 +1120,7 @@ static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, const NMLVCUS textcolor = comctl32_color.clrWindowText;
/* Set the text attributes */ - if (backcolor != CLR_NONE) - { - SetBkMode(hdc, OPAQUE); - SetBkColor(hdc, backcolor); - } - else - SetBkMode(hdc, TRANSPARENT); + SetBkColor(hdc, backcolor); SetTextColor(hdc, textcolor); }
@@ -5269,6 +5263,7 @@ static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcEra hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY); }
+ SetBkMode(hdc, TRANSPARENT); GetClientRect(infoPtr->hwndSelf, &rcClient); customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0); cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd); diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c index 8eb04ba55ef..5cb4d142ec5 100644 --- a/dlls/comctl32/tests/listview.c +++ b/dlls/comctl32/tests/listview.c @@ -2381,7 +2381,8 @@ static void test_customdraw_background(BOOL v6) flush_sequences(sequences, NUM_MSG_SEQUENCES); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); - ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "Normal", TRUE); + ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "Normal", + v6 && (test_styles[i] & LVS_TYPEMASK) == LVS_REPORT);
/* LVIS_SELECTED */ item.mask = LVIF_STATE; @@ -2392,7 +2393,8 @@ static void test_customdraw_background(BOOL v6) flush_sequences(sequences, NUM_MSG_SEQUENCES); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); - ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "LVIS_SELECTED", TRUE); + ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "LVIS_SELECTED", + v6 && (test_styles[i] & LVS_TYPEMASK) == LVS_REPORT);
/* LVS_SHOWSELALWAYS */ SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) | LVS_SHOWSELALWAYS); @@ -2400,7 +2402,8 @@ static void test_customdraw_background(BOOL v6) flush_sequences(sequences, NUM_MSG_SEQUENCES); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); - ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "LVS_SHOWSELALWAYS", TRUE); + ok_sequence(sequences, PARENT_CD_SEQ_INDEX, expected_msgs, "LVS_SHOWSELALWAYS", + v6 && (test_styles[i] & LVS_TYPEMASK) == LVS_REPORT);
SetWindowLongPtrA(hwndparent, GWLP_WNDPROC, (LONG_PTR)oldwndproc); flush_sequences(sequences, NUM_MSG_SEQUENCES);
On Mon Jun 16 11:24:31 2025 +0000, Nikolay Sivov wrote:
I don't think we can do that unconditionally, a lot of application were never tested with v5 and very likely depend on such differences. I know that we don't run some of the painting tests on v6 at the moment. Splitting them up and having a separate test for background mode that runs on v5 and v6 is what we should do. We might do a separate v6 build in theory, but I suspect that will be avoided for as long as possible. It should be enough to check for windows class in current context in WM_NCCREATE (and maybe for actual class name too if it's possible to use versioned name directly, I don't remember).
I added tests for v6. The comctl32 v6 listview with LVS_REPORT style will send some custom draw messages first with OPAQUE mode to draw headers. Then it will send another custom draw message sequence to draw items. We don't do that on Wine currently. To fix this properly, we will need to have two implementations for v5 and v6. Then, for v6, we need to send messages to draw headers first with OPAQUE. Also, 32-bit and 64-bit comctl32 v6 listviews have different behaviors. I am not sure whether we should match these behaviors now. We will probably need to separate comctl32 into v5 and v6. I am sending an update with TRANSPARENT set for all custom draw messages. It's at least a bit closer to what the native does.
Nikolay Sivov (@nsivov) commented about dlls/comctl32/tests/listview.c:
if (v6)
{
if (test_styles[i] == LVS_REPORT)
{
if (is_64bit)
expected_msgs = v6_64bit_parent_report_cd_bk_seq;
else
expected_msgs = v6_32bit_parent_report_cd_bk_seq;
}
else if (test_styles[i] == (LVS_REPORT | LVS_OWNERDRAWFIXED))
{
if (is_64bit)
expected_msgs = v6_64bit_ownerdrawfixed_parent_list_cd_bk_seq;
else
expected_msgs = v6_32bit_ownerdrawfixed_parent_list_cd_bk_seq;
}
Could we check BkMode some other way, without different sequences. We are not going to replicate them anyway.