[PATCH v3 0/2] MR8258: comctl32/treeview: Return from TREEVIEW_LButtonDown when the treeview handle is invalid.
comctl32/treeview: Return from TREEVIEW_LButtonDown when the treeview handle is invalid. LButtonDown should return when the treeview handle is invalid (e.g. destroyed) after NM_CLICK to prevent further message processing. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58211 -- v3: comctl32/treeview: Return from TREEVIEW_LButtonDown when the treeview handle is invalid. https://gitlab.winehq.org/wine/wine/-/merge_requests/8258
From: Jacob Czekalla <jczekalla(a)codeweavers.com> --- dlls/comctl32/tests/treeview.c | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/dlls/comctl32/tests/treeview.c b/dlls/comctl32/tests/treeview.c index 5b53a32f51b..e6f8ca648f4 100644 --- a/dlls/comctl32/tests/treeview.c +++ b/dlls/comctl32/tests/treeview.c @@ -52,6 +52,7 @@ static char *g_endedit_overwrite_ptr; static HFONT g_customdraw_font; static BOOL g_v6; static int g_reject_tvn_itemexpanding = 0; +static int g_click_delete_test = 0; #define NUM_MSG_SEQUENCES 3 #define TREEVIEW_SEQ_INDEX 0 @@ -243,6 +244,20 @@ static const struct message test_right_click_seq[] = { { 0 } }; +static const struct message test_click_delete_seq[] = { + { WM_LBUTTONDOWN, sent|wparam, MK_LBUTTON }, + { WM_CAPTURECHANGED, sent|defwinproc }, + { 0x90, sent|defwinproc|optional }, + { WM_SHOWWINDOW, sent|defwinproc|wparam|lparam, 0, 0 }, + { WM_WINDOWPOSCHANGING, sent|defwinproc }, + { WM_WINDOWPOSCHANGED, sent|defwinproc }, + { WM_KILLFOCUS, sent|defwinproc }, + { WM_IME_SETCONTEXT, sent|defwinproc|wparam, 0 }, + { WM_DESTROY, sent|defwinproc }, + { WM_NCDESTROY, sent|defwinproc }, + { 0 } +}; + static const struct message parent_expand_seq[] = { { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMEXPANDINGA }, { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMEXPANDEDA }, @@ -1478,6 +1493,15 @@ static LRESULT CALLBACK parent_wnd_proc(HWND hWnd, UINT message, WPARAM wParam, ok(selected == hChild, "child item should still be selected\n"); break; } + case NM_CLICK: + { + if(g_click_delete_test) + { + DestroyWindow(pHdr->hwndFrom); + return FALSE; + } + break; + } } } break; @@ -3123,6 +3147,49 @@ static void test_right_click(void) DestroyWindow(hTree); } +static void test_treeview_delete_midclick(void) +{ + HWND treeview; + RECT rc; + POINT pt, orig_pos; + MSG msg; + + g_click_delete_test = 1; + treeview = create_treeview_control(0); + fill_tree(treeview); + ShowWindow(hMainWnd, SW_SHOW); + UpdateWindow(hMainWnd); + + *(HTREEITEM*)&rc = hRoot; + SendMessageA(treeview, TVM_GETITEMRECT, TRUE, (LPARAM)&rc); + + pt.x = (rc.left + rc.right) / 2; + pt.y = (rc.top + rc.bottom) / 2; + ClientToScreen(hMainWnd, &pt); + GetCursorPos(&orig_pos); + SetCursorPos(pt.x, pt.y); + ScreenToClient(treeview, &pt); + + flush_events(); + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + PostMessageA(treeview, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y)); + PostMessageA(treeview, WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + while(GetMessageA(&msg, 0, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + if((msg.hwnd == treeview) && (msg.message == WM_LBUTTONDOWN)) + break; + } + + flush_events(); + ok_sequence(sequences, TREEVIEW_SEQ_INDEX, test_click_delete_seq, "treeview click and destroy sequence", TRUE); + + SetCursorPos(orig_pos.x, orig_pos.y); + g_click_delete_test = 0; +} + static void init_functions(void) { HMODULE hComCtl32 = LoadLibraryA("comctl32.dll"); @@ -3206,6 +3273,7 @@ START_TEST(treeview) test_TVS_FULLROWSELECT(); test_TVM_SORTCHILDREN(); test_right_click(); + test_treeview_delete_midclick(); if (!load_v6_module(&ctx_cookie, &hCtx)) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8258
From: Jacob Czekalla <jczekalla(a)codeweavers.com> LButtonDown should return when the treeview handle is invalid (e.g. destroyed) after NM_CLICK to prevent further message processing. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58211 --- dlls/comctl32/tests/treeview.c | 2 +- dlls/comctl32/treeview.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dlls/comctl32/tests/treeview.c b/dlls/comctl32/tests/treeview.c index e6f8ca648f4..2708bffb669 100644 --- a/dlls/comctl32/tests/treeview.c +++ b/dlls/comctl32/tests/treeview.c @@ -3184,7 +3184,7 @@ static void test_treeview_delete_midclick(void) } flush_events(); - ok_sequence(sequences, TREEVIEW_SEQ_INDEX, test_click_delete_seq, "treeview click and destroy sequence", TRUE); + ok_sequence(sequences, TREEVIEW_SEQ_INDEX, test_click_delete_seq, "treeview click and destroy sequence", FALSE); SetCursorPos(orig_pos.x, orig_pos.y); g_click_delete_test = 0; diff --git a/dlls/comctl32/treeview.c b/dlls/comctl32/treeview.c index 073f62c66a2..03fff6bcd7e 100644 --- a/dlls/comctl32/treeview.c +++ b/dlls/comctl32/treeview.c @@ -4315,7 +4315,7 @@ TREEVIEW_LButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam) (ht.flags & TVHT_ONITEMLABEL) && (infoPtr->selectedItem == ht.hItem); /* Send NM_CLICK right away */ - if (!do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) + if ((!do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) || !IsWindow(hwnd)) goto setfocus; if (ht.flags & TVHT_ONITEMBUTTON) @@ -4345,7 +4345,7 @@ TREEVIEW_LButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam) } } - if (do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) + if ((do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) || !IsWindow(hwnd)) goto setfocus; if (TREEVIEW_IsFullRowSelect(infoPtr)) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8258
Zhiyi Zhang (@zhiyi) commented about dlls/comctl32/tests/treeview.c:
ok(selected == hChild, "child item should still be selected\n"); break; } + case NM_CLICK: + { + if(g_click_delete_test)
Add a space after `if`. Same at other places. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/8258#note_106674
Zhiyi Zhang (@zhiyi) commented about dlls/comctl32/tests/treeview.c:
+ + flush_events(); + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + PostMessageA(treeview, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y)); + PostMessageA(treeview, WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + while(GetMessageA(&msg, 0, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + if((msg.hwnd == treeview) && (msg.message == WM_LBUTTONDOWN)) + break; + } + + flush_events(); + ok_sequence(sequences, TREEVIEW_SEQ_INDEX, test_click_delete_seq, "treeview click and destroy sequence", TRUE); Let's add a `flush_sequences(sequences, NUM_MSG_SEQUENCES);` here.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/8258#note_106676
Zhiyi Zhang (@zhiyi) commented about dlls/comctl32/treeview.c:
} }
- if (do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) + if ((do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) || !IsWindow(hwnd))
Calling `SetFocus(hwnd)` after the window is destroyed is probably wrong. It seems better to just return without doing anything when the window is destroyed. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/8258#note_106677
Zhiyi Zhang (@zhiyi) commented about dlls/comctl32/tests/treeview.c:
}
+static void test_treeview_delete_midclick(void) +{ + HWND treeview; + RECT rc; + POINT pt, orig_pos; + MSG msg; + + g_click_delete_test = 1; + treeview = create_treeview_control(0); + fill_tree(treeview); + ShowWindow(hMainWnd, SW_SHOW); + UpdateWindow(hMainWnd); + + *(HTREEITEM*)&rc = hRoot; Add a space between `HTREEITEM` and `*`
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/8258#note_106675
On Mon Jun 16 09:47:59 2025 +0000, Zhiyi Zhang wrote:
Calling `SetFocus(hwnd)` after the window is destroyed is probably wrong. It seems better to just return without doing anything when the window is destroyed. I did that to reduce the number of lines. SetFocus can be passed a NULL or invalid handle, but I can change it as it may look wonky.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/8258#note_106711
participants (3)
-
Jacob Czekalla -
Jacob Czekalla (@JacobCzekalla) -
Zhiyi Zhang (@zhiyi)