[PATCH 3/7] comctl32/tests: Add themed background tests.

Zhiyi Zhang zzhang at codeweavers.com
Wed Feb 9 02:44:42 CST 2022


Add themed background tests to determine whether DrawThemeParentBackground()
or WM_CTLCOLOR* are used to draw themed background for comctl32 classes.

Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/tests/misc.c | 327 +++++++++++++++++++++++++++++++++++++
 1 file changed, 327 insertions(+)

diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c
index 4628e37bb7a..3a1b5253b58 100644
--- a/dlls/comctl32/tests/misc.c
+++ b/dlls/comctl32/tests/misc.c
@@ -46,6 +46,7 @@ static HMODULE hComctl32;
 enum seq_index
 {
     CHILD_SEQ_INDEX,
+    PARENT_SEQ_INDEX,
     NUM_MSG_SEQUENCES
 };
 
@@ -652,6 +653,331 @@ static void test_WM_SYSCOLORCHANGE(void)
     DestroyWindow(parent);
 }
 
+static const struct message empty_seq[] =
+{
+    {0}
+};
+
+static const struct message wm_erasebkgnd_seq[] =
+{
+    {WM_ERASEBKGND, sent},
+    {0}
+};
+
+static const struct message wm_ctlcolorstatic_seq[] =
+{
+    {WM_CTLCOLORSTATIC, sent},
+    {0}
+};
+
+static const struct message drawthemeparentbackground_seq[] =
+{
+    {WM_ERASEBKGND, sent},
+    {WM_PRINTCLIENT, sent},
+    {0}
+};
+
+static const struct message drawthemeparentbackground_optional_seq[] =
+{
+    {WM_ERASEBKGND, sent | optional},
+    {WM_PRINTCLIENT, sent | optional},
+    {0}
+};
+
+static const struct message pushbutton_seq[] =
+{
+    {WM_ERASEBKGND, sent},
+    {WM_PRINTCLIENT, sent},
+    {WM_CTLCOLORBTN, sent},
+    {0}
+};
+
+static const struct message defpushbutton_seq[] =
+{
+    {WM_ERASEBKGND, sent},
+    {WM_PRINTCLIENT, sent},
+    {WM_CTLCOLORBTN, sent},
+    {WM_ERASEBKGND, sent | optional},
+    {WM_PRINTCLIENT, sent | optional},
+    {WM_CTLCOLORBTN, sent | optional},
+    {0}
+};
+
+static const struct message checkbox_seq[] =
+{
+    {WM_ERASEBKGND, sent | optional},
+    {WM_PRINTCLIENT, sent | optional},
+    {WM_CTLCOLORSTATIC, sent},
+    {0}
+};
+
+static const struct message radiobutton_seq[] =
+{
+    {WM_ERASEBKGND, sent},
+    {WM_PRINTCLIENT, sent},
+    {WM_CTLCOLORSTATIC, sent},
+    {0}
+};
+
+static const struct message groupbox_seq[] =
+{
+    {WM_CTLCOLORSTATIC, sent},
+    {WM_ERASEBKGND, sent},
+    {WM_PRINTCLIENT, sent},
+    {0}
+};
+
+static const struct message ownerdrawbutton_seq[] =
+{
+    {WM_CTLCOLORBTN, sent},
+    {WM_CTLCOLORBTN, sent},
+    {0}
+};
+
+static const struct message splitbutton_seq[] =
+{
+    {WM_ERASEBKGND, sent},
+    {WM_PRINTCLIENT, sent},
+    /* Either WM_CTLCOLORSTATIC or WM_CTLCOLORBTN */
+    {WM_CTLCOLORSTATIC, sent | optional},
+    {WM_CTLCOLORBTN, sent | optional},
+    /* BS_DEFSPLITBUTTON or BS_DEFCOMMANDLINK */
+    {WM_ERASEBKGND, sent | optional},
+    {WM_PRINTCLIENT, sent | optional},
+    {WM_CTLCOLORSTATIC, sent | optional},
+    {WM_CTLCOLORBTN, sent | optional},
+    {0}
+};
+
+static const struct message combobox_seq[] =
+{
+    {WM_ERASEBKGND, sent | optional},
+    {WM_PRINTCLIENT, sent | optional},
+    {WM_CTLCOLOREDIT, sent},
+    {WM_CTLCOLORLISTBOX, sent},
+    {WM_CTLCOLORLISTBOX, sent | optional},
+    {WM_CTLCOLOREDIT, sent | optional},
+    {WM_CTLCOLOREDIT, sent | optional},
+    {0}
+};
+
+static const struct message edit_seq[] =
+{
+    {WM_CTLCOLOREDIT, sent},
+    {WM_CTLCOLOREDIT, sent | optional},
+    {0}
+};
+
+static const struct message listbox_seq[] =
+{
+    {WM_CTLCOLORLISTBOX, sent},
+    {WM_CTLCOLORLISTBOX, sent},
+    {0}
+};
+
+static const struct message treeview_seq[] =
+{
+    {WM_CTLCOLOREDIT, sent | optional},
+    {WM_CTLCOLOREDIT, sent | optional},
+    {0}
+};
+
+static const struct message scrollbar_seq[] =
+{
+    {WM_CTLCOLORSCROLLBAR, sent},
+    {WM_CTLCOLORSCROLLBAR, sent | optional},
+    {0}
+};
+
+static LRESULT WINAPI test_themed_background_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
+{
+    struct message msg = {0};
+    HBRUSH brush;
+    RECT rect;
+
+    if (message == WM_ERASEBKGND || message == WM_PRINTCLIENT || (message >= WM_CTLCOLORMSGBOX
+        && message <= WM_CTLCOLORSTATIC))
+    {
+        msg.message = message;
+        msg.flags = sent;
+        add_message(sequences, PARENT_SEQ_INDEX, &msg);
+    }
+
+    if (message == WM_ERASEBKGND)
+    {
+        brush = CreateSolidBrush(RGB(255, 0, 0));
+        GetClientRect(hwnd, &rect);
+        FillRect((HDC)wp, &rect, brush);
+        DeleteObject(brush);
+        return 1;
+    }
+    else if (message >= WM_CTLCOLORMSGBOX && message <= WM_CTLCOLORSTATIC)
+    {
+        return (LRESULT)GetStockObject(GRAY_BRUSH);
+    }
+
+    return DefWindowProcA(hwnd, message, wp, lp);
+}
+
+static void test_themed_background(void)
+{
+    DPI_AWARENESS_CONTEXT (WINAPI *pSetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
+    DPI_AWARENESS_CONTEXT old_context = NULL;
+    BOOL (WINAPI *pIsThemeActive)(void);
+    HWND child, parent;
+    HMODULE uxtheme;
+    COLORREF color;
+    WNDCLASSA cls;
+    HDC hdc;
+    int i;
+
+    static const struct test
+    {
+        const CHAR *class_name;
+        DWORD style;
+        const struct message *seq;
+        BOOL todo;
+    }
+    tests[] =
+    {
+        {ANIMATE_CLASSA, 0, empty_seq, TRUE},
+        {WC_BUTTONA, BS_PUSHBUTTON, pushbutton_seq, TRUE},
+        {WC_BUTTONA, BS_DEFPUSHBUTTON, defpushbutton_seq, TRUE},
+        {WC_BUTTONA, BS_CHECKBOX, checkbox_seq, TRUE},
+        {WC_BUTTONA, BS_AUTOCHECKBOX, checkbox_seq, TRUE},
+        {WC_BUTTONA, BS_RADIOBUTTON, radiobutton_seq, TRUE},
+        {WC_BUTTONA, BS_3STATE, checkbox_seq, TRUE},
+        {WC_BUTTONA, BS_AUTO3STATE, checkbox_seq, TRUE},
+        {WC_BUTTONA, BS_GROUPBOX, groupbox_seq, TRUE},
+        {WC_BUTTONA, BS_USERBUTTON, pushbutton_seq, TRUE},
+        {WC_BUTTONA, BS_AUTORADIOBUTTON, radiobutton_seq, TRUE},
+        {WC_BUTTONA, BS_PUSHBOX, radiobutton_seq, TRUE},
+        {WC_BUTTONA, BS_OWNERDRAW, ownerdrawbutton_seq},
+        {WC_BUTTONA, BS_SPLITBUTTON, splitbutton_seq},
+        {WC_BUTTONA, BS_DEFSPLITBUTTON, splitbutton_seq},
+        {WC_BUTTONA, BS_COMMANDLINK, splitbutton_seq},
+        {WC_BUTTONA, BS_DEFCOMMANDLINK, splitbutton_seq},
+        {WC_COMBOBOXA, 0, combobox_seq, TRUE},
+        {WC_COMBOBOXEXA, 0, drawthemeparentbackground_optional_seq},
+        {DATETIMEPICK_CLASSA, 0, drawthemeparentbackground_optional_seq, TRUE},
+        {WC_EDITA, 0, edit_seq},
+        {WC_HEADERA, 0, empty_seq},
+        {HOTKEY_CLASSA, 0, empty_seq, TRUE},
+        {WC_IPADDRESSA, 0, empty_seq},
+        {WC_LISTBOXA, 0, listbox_seq, TRUE},
+        {WC_LISTVIEWA, 0, empty_seq},
+        {MONTHCAL_CLASSA, 0, empty_seq},
+        {WC_NATIVEFONTCTLA, 0, empty_seq},
+        {WC_PAGESCROLLERA, 0, wm_erasebkgnd_seq},
+        {PROGRESS_CLASSA, 0, drawthemeparentbackground_optional_seq},
+        {REBARCLASSNAMEA, 0, empty_seq},
+        {WC_STATICA, SS_LEFT, wm_ctlcolorstatic_seq},
+        {WC_STATICA, SS_ICON, wm_ctlcolorstatic_seq},
+        {WC_STATICA, SS_BLACKRECT, wm_ctlcolorstatic_seq, TRUE},
+        {WC_STATICA, SS_OWNERDRAW, wm_ctlcolorstatic_seq},
+        {WC_STATICA, SS_BITMAP, wm_ctlcolorstatic_seq},
+        {WC_STATICA, SS_ENHMETAFILE, wm_ctlcolorstatic_seq},
+        {WC_STATICA, SS_ETCHEDHORZ, wm_ctlcolorstatic_seq, TRUE},
+        {STATUSCLASSNAMEA, 0, empty_seq},
+        {"SysLink", 0, wm_ctlcolorstatic_seq},
+        {WC_TABCONTROLA, 0, drawthemeparentbackground_seq, TRUE},
+        {TOOLBARCLASSNAMEA, 0, empty_seq, TRUE},
+        {TOOLTIPS_CLASSA, 0, empty_seq},
+        {TRACKBAR_CLASSA, 0, wm_ctlcolorstatic_seq, TRUE},
+        {WC_TREEVIEWA, 0, treeview_seq},
+        {UPDOWN_CLASSA, 0, empty_seq},
+        {WC_SCROLLBARA, 0, scrollbar_seq, TRUE},
+        {WC_SCROLLBARA, SBS_SIZEBOX, empty_seq, TRUE},
+        {WC_SCROLLBARA, SBS_SIZEGRIP, empty_seq, TRUE},
+    };
+
+    uxtheme = LoadLibraryA("uxtheme.dll");
+    pIsThemeActive = (void *)GetProcAddress(uxtheme, "IsThemeActive");
+    if (!pIsThemeActive())
+    {
+        skip("Theming is inactive.\n");
+        FreeLibrary(uxtheme);
+        return;
+    }
+    FreeLibrary(uxtheme);
+
+    pSetThreadDpiAwarenessContext = (void *)GetProcAddress(GetModuleHandleA("user32.dll"),
+                                                           "SetThreadDpiAwarenessContext");
+    if (pSetThreadDpiAwarenessContext)
+        pSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
+
+    memset(&cls, 0, sizeof(cls));
+    cls.hInstance = GetModuleHandleA(0);
+    cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
+    cls.hbrBackground = GetStockObject(WHITE_BRUSH);
+    cls.lpfnWndProc = test_themed_background_proc;
+    cls.lpszClassName = "ParentClass";
+    RegisterClassA(&cls);
+
+    parent = CreateWindowA(cls.lpszClassName, "parent", WS_POPUP | WS_VISIBLE, 100, 100, 100, 100,
+                           0, 0, 0, 0);
+    ok(parent != NULL, "CreateWindowA failed, error %u.\n", GetLastError());
+
+    for (i = 0; i < ARRAY_SIZE(tests); ++i)
+    {
+        winetest_push_context("%s %#x", tests[i].class_name, tests[i].style);
+
+        child = CreateWindowA(tests[i].class_name, "    ", WS_CHILD | WS_VISIBLE | tests[i].style,
+                              0, 0, 50, 50, parent, 0, 0, 0);
+        ok(child != NULL, "CreateWindowA failed, error %u.\n", GetLastError());
+        flush_events();
+        flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+        RedrawWindow(child, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ERASENOW | RDW_FRAME);
+        ok_sequence(sequences, PARENT_SEQ_INDEX, tests[i].seq, "paint background", tests[i].todo);
+
+        /* For message sequences that contain both DrawThemeParentBackground() messages and
+         * WM_CTLCOLOR*, do a color test to check which is really in effect for controls that can be
+         * tested automatically. For WM_ERASEBKGND from DrawThemeParentBackground(), a red brush is
+         * used. For WM_CTLCOLOR*, a gray brush is returned. If there are only WM_CTLCOLOR* messages
+         * in the message sequence, then surely DrawThemeParentBackground() is not used.
+         *
+         * For tests that use pushbutton_seq and defpushbutton_seq, manual tests on XP show that
+         * a brush from WM_CTLCOLORBTN is used to fill background even after a successful
+         * DrawThemeParentBackground(). This behavior can be verified by returning a gray or hollow
+         * brush in test_themed_background_proc() when handling WM_CTLCOLORBTN. It can't be tested
+         * automatically here because stock Windows themes don't use transparent button bitmaps */
+        if (tests[i].seq == radiobutton_seq || tests[i].seq == groupbox_seq)
+        {
+            hdc = GetDC(child);
+
+            if (tests[i].seq == radiobutton_seq)
+            {
+                /* WM_CTLCOLORSTATIC is used to fill background */
+                color = GetPixel(hdc, 40, 40);
+                todo_wine
+                ok(color == 0x808080, "Expected color %#x, got %#x.\n", 0x808080, color);
+            }
+            else if (tests[i].seq == groupbox_seq)
+            {
+                /* DrawThemeParentBackground() is used to fill content background */
+                color = GetPixel(hdc, 40, 40);
+                ok(color == 0xff, "Expected color %#x, got %#x.\n", 0xff, color);
+
+                /* WM_CTLCOLORSTATIC is used to fill text background */
+                color = GetPixel(hdc, 10, 10);
+                todo_wine
+                ok(color == 0x808080, "Expected color %#x, got %#x.\n", 0x808080, color);
+            }
+
+            ReleaseDC(child, hdc);
+        }
+
+        DestroyWindow(child);
+        winetest_pop_context();
+    }
+
+    DestroyWindow(parent);
+    UnregisterClassA(cls.lpszClassName, GetModuleHandleA(0));
+    if (pSetThreadDpiAwarenessContext)
+        pSetThreadDpiAwarenessContext(old_context);
+}
+
 START_TEST(misc)
 {
     ULONG_PTR ctx_cookie;
@@ -675,6 +1001,7 @@ START_TEST(misc)
     test_comctl32_classes(TRUE);
     test_builtin_classes();
     test_LoadIconWithScaleDown();
+    test_themed_background();
     test_WM_THEMECHANGED();
     test_WM_SYSCOLORCHANGE();
 
-- 
2.32.0




More information about the wine-devel mailing list