From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/listview.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c index 54cd452ce81..216c97b6774 100644 --- a/dlls/comctl32/listview.c +++ b/dlls/comctl32/listview.c @@ -5629,6 +5629,13 @@ static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy) } LISTVIEW_InvalidateList(infoPtr); infoPtr->bNoItemMetrics = TRUE; + + if (!destroy) + { + NotifyWinEvent( EVENT_OBJECT_REORDER, infoPtr->hwndSelf, OBJID_CLIENT, 0 ); + NotifyWinEvent( EVENT_OBJECT_DESTROY, infoPtr->hwndSelf, OBJID_CLIENT, 0 ); + NotifyWinEvent( EVENT_OBJECT_CREATE, infoPtr->hwndSelf, OBJID_CLIENT, 0 ); + }
return TRUE; } @@ -5920,6 +5927,9 @@ static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem) /* now is the invalidation fun */ if (!is_icon) LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1); + + NotifyWinEvent( EVENT_OBJECT_DESTROY, infoPtr->hwndSelf, OBJID_CLIENT, nItem + 1 ); + return TRUE; }
@@ -7817,7 +7827,11 @@ static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
- if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++; + if (infoPtr->dwStyle & LVS_OWNERDATA) + { + NotifyWinEvent( EVENT_OBJECT_CREATE, hwndSelf, OBJID_CLIENT, infoPtr->nItemCount + 1 ); + return infoPtr->nItemCount++; + }
/* make sure it's an item, and not a subitem; cannot insert a subitem */ if (!lpLVItem || lpLVItem->iSubItem) return -1; @@ -7949,6 +7963,9 @@ static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
/* now is the invalidation fun */ LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1); + + NotifyWinEvent( EVENT_OBJECT_CREATE, hwndSelf, OBJID_CLIENT, nItem + 1 ); + return nItem;
undo: @@ -9353,6 +9370,8 @@ static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView) LISTVIEW_UpdateScroll(infoPtr); LISTVIEW_InvalidateList(infoPtr);
+ NotifyWinEvent( EVENT_OBJECT_REORDER, infoPtr->hwndSelf, OBJID_CLIENT, 0 ); + TRACE("nView %ld\n", nView);
return 1;
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/tests/listview.c | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+)
diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c index a0340dbd5c3..38fbee3989c 100644 --- a/dlls/comctl32/tests/listview.c +++ b/dlls/comctl32/tests/listview.c @@ -101,6 +101,48 @@ static void init_functions(void)
static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];
+static void CALLBACK msg_winevent_proc(HWINEVENTHOOK hevent, + DWORD event, + HWND hwnd, + LONG object_id, + LONG child_id, + DWORD thread_id, + DWORD event_time) +{ + struct message msg = {0}; + char class_name[256]; + + /* ignore window and other system events */ + if (object_id != OBJID_CLIENT) return; + + /* ignore events not from a listview control */ + if (!GetClassNameA(hwnd, class_name, ARRAY_SIZE(class_name)) || + strcmp(class_name, WC_LISTVIEWA) != 0) + return; + + msg.message = event; + msg.flags = winevent_hook|wparam|lparam; + msg.wParam = object_id; + msg.lParam = child_id; + add_message(sequences, LISTVIEW_SEQ_INDEX, &msg); + add_message(sequences, COMBINED_SEQ_INDEX, &msg); +} + +static void init_winevent_hook(void) { + hwineventhook = SetWinEventHook(EVENT_MIN, EVENT_MAX, GetModuleHandleA(0), msg_winevent_proc, + 0, GetCurrentThreadId(), WINEVENT_INCONTEXT); + if (!hwineventhook) + win_skip( "no win event hook support\n" ); +} + +static void uninit_winevent_hook(void) { + if (!hwineventhook) + return; + + UnhookWinEvent(hwineventhook); + hwineventhook = 0; +} + static const struct message create_ownerdrawfixed_parent_seq[] = { { WM_NOTIFYFORMAT, sent }, { WM_QUERYUISTATE, sent|optional }, /* Win2K and higher */ @@ -160,27 +202,40 @@ static const struct message listview_color_seq[] = { static const struct message listview_item_count_seq[] = { { LVM_GETITEMCOUNT, sent }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 1 }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 2 }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 3 }, { LVM_GETITEMCOUNT, sent }, { LVM_DELETEITEM, sent|wparam, 2 }, + { EVENT_OBJECT_DESTROY, winevent_hook|wparam|lparam, OBJID_CLIENT, 3 }, { WM_NCPAINT, sent|optional }, { WM_ERASEBKGND, sent|optional }, { LVM_GETITEMCOUNT, sent }, { LVM_DELETEALLITEMS, sent }, + { EVENT_OBJECT_REORDER, winevent_hook|wparam|lparam, OBJID_CLIENT, 0 }, + { EVENT_OBJECT_DESTROY, winevent_hook|wparam|lparam, OBJID_CLIENT, 0 }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 0 }, { LVM_GETITEMCOUNT, sent }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 1 }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 2 }, { LVM_GETITEMCOUNT, sent }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 3 }, { LVM_GETITEMCOUNT, sent }, { 0 } };
static const struct message listview_itempos_seq[] = { { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 1 }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 2 }, { LVM_INSERTITEMA, sent }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 3 }, { LVM_SETITEMPOSITION, sent|wparam|lparam, 1, MAKELPARAM(10,5) }, { WM_NCPAINT, sent|optional }, { WM_ERASEBKGND, sent|optional }, @@ -462,6 +517,9 @@ static const struct message listview_ownerdata_destroy[] = { static const struct message listview_ownerdata_deleteall[] = { { LVM_DELETEALLITEMS, sent }, { WM_NOTIFY, sent|id, 0, 0, LVN_DELETEALLITEMS }, + { EVENT_OBJECT_REORDER, winevent_hook|wparam|lparam, OBJID_CLIENT, 0 }, + { EVENT_OBJECT_DESTROY, winevent_hook|wparam|lparam, OBJID_CLIENT, 0 }, + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_CLIENT, 0 }, { 0 } };
@@ -7401,6 +7459,8 @@ START_TEST(listview)
init_msg_sequences(sequences, NUM_MSG_SEQUENCES);
+ init_winevent_hook(); + hwndparent = create_parent_window(FALSE); flush_sequences(sequences, NUM_MSG_SEQUENCES);
@@ -7513,6 +7573,8 @@ START_TEST(listview) test_LVM_GETHOTCURSOR(); test_LVM_GETORIGIN(TRUE);
+ uninit_winevent_hook(); + unload_v6_module(ctx_cookie, hCtx);
DestroyWindow(hwndparent);
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/tests/listview.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c index 38fbee3989c..e9dba881293 100644 --- a/dlls/comctl32/tests/listview.c +++ b/dlls/comctl32/tests/listview.c @@ -468,6 +468,19 @@ static const struct message scroll_parent_seq[] = { { 0 } };
+static const struct message listview_setview_seq[] = { + { LVM_SETVIEW, sent|wparam|lparam, 1, 0 }, + { WM_NOTIFYFORMAT, sent|defwinproc|lparam|optional, 0, NF_QUERY }, + { WM_NOTIFYFORMAT, sent|defwinproc|lparam|optional, 0, NF_QUERY }, + { WM_QUERYUISTATE, sent|defwinproc|wparam|lparam|optional, 0, 0 }, + { WM_PARENTNOTIFY, sent|defwinproc|optional }, + { WM_PARENTNOTIFY, sent|defwinproc|optional }, + { WM_PARENTNOTIFY, sent|defwinproc|optional }, + { EVENT_OBJECT_REORDER, winevent_hook|wparam|lparam, OBJID_CLIENT, 0 }, + { WM_ERASEBKGND, sent|defwinproc|optional }, + { 0 } +}; + static const struct message setredraw_seq[] = { { WM_SETREDRAW, sent|id|wparam, FALSE, 0, LISTVIEW_ID }, { 0 } @@ -5034,8 +5047,11 @@ static void test_get_set_view(void) expect(LV_VIEW_LIST, ret);
/* switching view doesn't touch window style */ + flush_sequences(sequences, NUM_MSG_SEQUENCES); ret = SendMessageA(hwnd, LVM_SETVIEW, LV_VIEW_DETAILS, 0); expect(1, ret); + ok_sequence(sequences, LISTVIEW_SEQ_INDEX, listview_setview_seq, "test setview seq", FALSE); + style = GetWindowLongPtrA(hwnd, GWL_STYLE); ok(style & LVS_LIST, "Expected style to be preserved\n"); ret = SendMessageA(hwnd, LVM_SETVIEW, LV_VIEW_ICON, 0);
Zhiyi Zhang (@zhiyi) commented about dlls/comctl32/listview.c:
} LISTVIEW_InvalidateList(infoPtr); infoPtr->bNoItemMetrics = TRUE;
- if (!destroy)
What about when destroy is TRUE?
Nikolay Sivov (@nsivov) commented about dlls/comctl32/listview.c:
} LISTVIEW_InvalidateList(infoPtr); infoPtr->bNoItemMetrics = TRUE;
- if (!destroy)
- {
NotifyWinEvent( EVENT_OBJECT_REORDER, infoPtr->hwndSelf, OBJID_CLIENT, 0 );
NotifyWinEvent( EVENT_OBJECT_DESTROY, infoPtr->hwndSelf, OBJID_CLIENT, 0 );
NotifyWinEvent( EVENT_OBJECT_CREATE, infoPtr->hwndSelf, OBJID_CLIENT, 0 );
- }
It's possible LVM_ARRANGE is what's causing REORDER event. But I'm not sure how deep we want to go with this, there are too many paths.
On Thu Apr 17 09:30:33 2025 +0000, Zhiyi Zhang wrote:
What about when destroy is TRUE? Could you add some tests?
There are already tests that fail without this check. If the window is destroyed, user32/win32u will send EVENT_OBJECT_DESTROY (not tested in this file because it's a window system event), making these events moot.
On Thu Apr 17 10:11:32 2025 +0000, Nikolay Sivov wrote:
It's possible LVM_ARRANGE is what's causing REORDER event. But I'm not sure how deep we want to go with this, there are too many paths.
Could be. I could see doing that if we have enough test coverage to actually work out those details. For now, I don't think it really matters, as long as we send the events we're supposed to, in the order we're supposed to, and only at times when the control is in a consistent state with the change already in effect.
This merge request was approved by Nikolay Sivov.