[PATCH v12 0/2] MR10451: comctl32/treeview: Implemented TVN_ITEMCHANGING & TVN_ITEMCHANGED
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59456 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=55707 -- v12: comctl32/treeview: Implemented TVN_ITEMCHANGING and TVN_ITEMCHANGED. comctl32/tests: Add tests for TVN_ITEMCHANGING and TVN_ITEMCHANGED. https://gitlab.winehq.org/wine/wine/-/merge_requests/10451
From: Piotr Pawłowski <p@perkele.cc> --- dlls/comctl32/tests/treeview.c | 134 ++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/dlls/comctl32/tests/treeview.c b/dlls/comctl32/tests/treeview.c index 997efa01a9d..5e3119c8fc2 100644 --- a/dlls/comctl32/tests/treeview.c +++ b/dlls/comctl32/tests/treeview.c @@ -423,9 +423,67 @@ static const struct message parent_right_click_seq[] = { { 0 } }; +static const struct message parent_itemchange[] = { + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGINGA }, + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGEDA }, + { WM_CTLCOLOREDIT, sent|optional }, + { WM_CTLCOLOREDIT, sent|optional }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGINGA }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMEXPANDINGA }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMEXPANDEDA }, + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGEDA }, + { WM_CTLCOLOREDIT, sent|optional }, + { WM_CTLCOLOREDIT, sent|optional }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { 0 } +}; + +/* +* TVN_ITEMCHANGINGW and TVN_ITEMCHANGEDW intended. +* TVN_ITEMCHANGINGA and TVN_ITEMCHANGEDA are never actually sent. +*/ +static const struct message parent_itemchange_v6[] = { + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGINGA }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMCHANGINGW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMCHANGEDW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGEDA }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGINGA }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMCHANGINGW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMCHANGEDW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMCHANGINGW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMCHANGEDW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMEXPANDINGA }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id, 0, 0, TVN_ITEMEXPANDEDA }, + { WM_NOTIFY, sent|id, 0, 0, TVN_SELCHANGEDA }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { WM_NOTIFY, sent|id|optional, 0, 0, NM_CUSTOMDRAW }, + { 0 } +}; + static HWND hMainWnd; -static HTREEITEM hRoot, hChild; +static HTREEITEM hRoot, hChild, hBlockChange; static int pos = 0; static char sequence[256]; @@ -745,6 +803,65 @@ static void test_select(void) DestroyWindow(hTree); } +/* Verify that TVN_ITEMCHANGINGW and TVN_ITEMCHANGEDW are sent if v6 and not sent otherwise. */ +static void test_itemchange_sequence(void) +{ + HWND hTree; + + hTree = create_treeview_control(0); + fill_tree(hTree); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + SendMessageA(hTree, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hRoot); + SendMessageA(hTree, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hChild); + + if ( g_v6 ) { + ok_sequence(sequences, PARENT_SEQ_INDEX, parent_itemchange_v6, "select item parent sequence v6", TRUE); + } else { + ok_sequence(sequences, PARENT_SEQ_INDEX, parent_itemchange, "select item parent sequence", FALSE); + } + + DestroyWindow(hTree); +} + +static void test_itemchanging_modify(void) +{ + BOOL r; + HWND hTree; + + hTree = create_treeview_control(0); + fill_tree(hTree); + + /* Test if we can prevent item being selected; TVN_ITEMCHANGING logic only in v6 */ + r = SendMessageA(hTree, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hRoot); + expect(TRUE, r); + r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hRoot, TVIS_SELECTED) & TVIS_SELECTED; + expect(TVIS_SELECTED, r); + hBlockChange = hChild; + r = SendMessageA(hTree, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hChild); + expect(TRUE, r); + r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hChild, TVIS_SELECTED) & TVIS_SELECTED; + if (g_v6) + todo_wine expect(0, r); + else + expect(TVIS_SELECTED, r); + + r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hRoot, TVIS_SELECTED) & TVIS_SELECTED; + expect(0, r); + hBlockChange = NULL; + r = SendMessageA(hTree, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hChild); + expect(TRUE, r); + r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hChild, TVIS_SELECTED) & TVIS_SELECTED; + if (g_v6) + todo_wine expect(0, r); + else + expect(TVIS_SELECTED, r); + r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hRoot, TVIS_SELECTED) & TVIS_SELECTED; + expect(0, r); + + DestroyWindow(hTree); +} + static void test_getitemtext(void) { TVINSERTSTRUCTA ins; @@ -1318,6 +1435,17 @@ static LRESULT CALLBACK parent_wnd_proc(HWND hWnd, UINT message, WPARAM wParam, NMTREEVIEWA *pTreeView = (LPNMTREEVIEWA) lParam; switch(pHdr->code) { + /* + * TVN_ITEMCHANGINGW (not TVN_ITEMCHANGINGA) intended. + * MS implementation appears to send TVN_ITEMCHANGINGW only. + * Available only in comctl32 v6. + */ + case TVN_ITEMCHANGINGW: + { + NMTVITEMCHANGE * pChange = (NMTVITEMCHANGE*) lParam; + if (pChange->hItem == hBlockChange) return TRUE; + } + break; case TVN_SELCHANGINGA: AddItem('('); IdentifyItem(pTreeView->itemOld.hItem); @@ -3411,6 +3539,8 @@ START_TEST(treeview) test_TVM_SORTCHILDREN(); test_right_click(); test_treeview_delete_midclick(); + test_itemchange_sequence(); + test_itemchanging_modify(); if (!load_v6_module(&ctx_cookie, &hCtx)) { @@ -3447,6 +3577,8 @@ START_TEST(treeview) test_TVS_FULLROWSELECT(); test_TVM_SORTCHILDREN(); test_treeview_delete_midclick(); + test_itemchange_sequence(); + test_itemchanging_modify(); unload_v6_module(ctx_cookie, hCtx); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10451
From: Piotr Pawłowski <p@perkele.cc> --- dlls/comctl32/tests/treeview.c | 6 +-- dlls/comctl32/treeview.c | 73 +++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/dlls/comctl32/tests/treeview.c b/dlls/comctl32/tests/treeview.c index 5e3119c8fc2..bc1d62e0fd2 100644 --- a/dlls/comctl32/tests/treeview.c +++ b/dlls/comctl32/tests/treeview.c @@ -816,7 +816,7 @@ static void test_itemchange_sequence(void) SendMessageA(hTree, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hChild); if ( g_v6 ) { - ok_sequence(sequences, PARENT_SEQ_INDEX, parent_itemchange_v6, "select item parent sequence v6", TRUE); + ok_sequence(sequences, PARENT_SEQ_INDEX, parent_itemchange_v6, "select item parent sequence v6", FALSE); } else { ok_sequence(sequences, PARENT_SEQ_INDEX, parent_itemchange, "select item parent sequence", FALSE); } @@ -842,7 +842,7 @@ static void test_itemchanging_modify(void) expect(TRUE, r); r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hChild, TVIS_SELECTED) & TVIS_SELECTED; if (g_v6) - todo_wine expect(0, r); + expect(0, r); else expect(TVIS_SELECTED, r); @@ -853,7 +853,7 @@ static void test_itemchanging_modify(void) expect(TRUE, r); r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hChild, TVIS_SELECTED) & TVIS_SELECTED; if (g_v6) - todo_wine expect(0, r); + expect(0, r); else expect(TVIS_SELECTED, r); r = SendMessageA(hTree, TVM_GETITEMSTATE, (WPARAM)hRoot, TVIS_SELECTED) & TVIS_SELECTED; diff --git a/dlls/comctl32/treeview.c b/dlls/comctl32/treeview.c index 6332040b7f5..d5418934eb2 100644 --- a/dlls/comctl32/treeview.c +++ b/dlls/comctl32/treeview.c @@ -517,6 +517,55 @@ TREEVIEW_SendRealNotify(const TREEVIEW_INFO *infoPtr, UINT code, NMHDR *hdr) return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, hdr->idFrom, (LPARAM)hdr); } +static BOOL TREEVIEW_SendItemChanging(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, UINT uChanged, UINT uStateOld, UINT uStateNew) +{ +#if __WINE_COMCTL32_VERSION == 6 + NMTVITEMCHANGE change; + change.uChanged = uChanged; + change.hItem = item; + change.uStateOld = uStateOld; + change.uStateNew = uStateNew; + change.lParam = item->lParam; + return TREEVIEW_SendRealNotify(infoPtr, TVN_ITEMCHANGINGW, &change.hdr); +#endif + return FALSE; +} + +static BOOL TREEVIEW_SendItemChanged(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, UINT uChanged, UINT uStateOld, UINT uStateNew) +{ +#if __WINE_COMCTL32_VERSION == 6 + NMTVITEMCHANGE change; + change.uChanged = uChanged; + change.hItem = item; + change.uStateOld = uStateOld; + change.uStateNew = uStateNew; + change.lParam = item->lParam; + return TREEVIEW_SendRealNotify(infoPtr, TVN_ITEMCHANGEDW, &change.hdr); +#endif + return FALSE; +} + +/* + * Change state helper + * Returns a boolean value indicating whether the change was accepted (always TRUE for legacy commoncontrols) + */ +static BOOL TREEVIEW_ChangeItemState(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, UINT stateNew) +{ + UINT state = item->state; + if (state != stateNew) + { + if (TREEVIEW_SendItemChanging(infoPtr, item, TVIF_STATE, state, stateNew)) return FALSE; + item->state = stateNew; + if (TREEVIEW_SendItemChanged(infoPtr, item, TVIF_STATE, state, stateNew)) + { + /* roll back */ + item->state = state; + return FALSE; + } + } + return TRUE; +} + /* * Returns TRUE if the TREEVIEW is still valid after the notify. * The notification result is stored in *result if non-NULL. @@ -1212,10 +1261,11 @@ TREEVIEW_DoSetItemT(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, if (tvItem->mask & TVIF_STATE) { - TRACE("prevstate 0x%x, state 0x%x, mask 0x%x\n", item->state, tvItem->state, + UINT stateNew; + TRACE("prevstate 0x%x, state 0x%x, mask 0x%x\n", item->state, tvItem->state, tvItem->stateMask); - item->state &= ~tvItem->stateMask; - item->state |= (tvItem->state & tvItem->stateMask); + stateNew = (item->state & ~tvItem->stateMask) | (tvItem->state & tvItem->stateMask); + TREEVIEW_ChangeItemState(infoPtr, item, stateNew); } if (tvItem->mask & TVIF_STATEEX) @@ -2338,7 +2388,7 @@ TREEVIEW_GetCount(const TREEVIEW_INFO *infoPtr) } static VOID -TREEVIEW_ToggleItemState(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +TREEVIEW_ToggleItemState(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) { if (infoPtr->dwStyle & TVS_CHECKBOXES) { @@ -2364,8 +2414,10 @@ TREEVIEW_ToggleItemState(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) TRACE("stateImage: 0x%x\n", stateImage); - item->state = state; - TREEVIEW_Invalidate(infoPtr, item); + if (state != item->state && TREEVIEW_ChangeItemState(infoPtr, item, state)) + { + TREEVIEW_Invalidate(infoPtr, item); + } } } @@ -4598,10 +4650,11 @@ TREEVIEW_DoSelectItem(TREEVIEW_INFO *infoPtr, INT action, HTREEITEM newSelect, newSelect)) return FALSE; - if (prevSelect) - prevSelect->state &= ~TVIS_SELECTED; - if (newSelect) - newSelect->state |= TVIS_SELECTED; + if (prevSelect == NULL || TREEVIEW_ChangeItemState( infoPtr, prevSelect, prevSelect->state & ~TVIS_SELECTED)) + { + /* deselected previous */ + if (newSelect != NULL) TREEVIEW_ChangeItemState( infoPtr, newSelect, newSelect->state | TVIS_SELECTED); + } infoPtr->selectedItem = newSelect; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10451
On Tue Jun 9 20:04:34 2026 +0000, Piotr Pawłowski wrote:
changed this line in [version 12 of the diff](/wine/wine/-/merge_requests/10451/diffs?diff_id=273842&start_sha=629e6f450e5da54ceb858d54740d268e52deeefe#ea6661805bd26d4797d9aebb9c2d0e45a9d9acc7_4653_4651) Fixed.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10451#note_142553
On Tue Jun 9 20:04:35 2026 +0000, Piotr Pawłowski wrote:
changed this line in [version 12 of the diff](/wine/wine/-/merge_requests/10451/diffs?diff_id=273842&start_sha=629e6f450e5da54ceb858d54740d268e52deeefe#ea6661805bd26d4797d9aebb9c2d0e45a9d9acc7_4652_4651) Fixed.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10451#note_142554
On Tue Jun 9 20:04:35 2026 +0000, Piotr Pawłowski wrote:
changed this line in [version 12 of the diff](/wine/wine/-/merge_requests/10451/diffs?diff_id=273842&start_sha=629e6f450e5da54ceb858d54740d268e52deeefe#ea6661805bd26d4797d9aebb9c2d0e45a9d9acc7_552_552) Done.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10451#note_142555
participants (2)
-
Piotr Pawłowski -
Piotr Pawłowski (@DEATH)