{"record":{"id":"8ed4cff9d0dac1b4","repo":"wailsapp/wails","slug":"sendmessage-lvm-update","errorCode":null,"errorMessage":"SendMessage(LVM_UPDATE)","messagePattern":"SendMessage\\(LVM_UPDATE\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v2/internal/frontend/desktop/windows/winc/listview.go","lineNumber":484,"sourceCode":"\t\t\t}\n\t\tcase w32.NM_DBLCLK:\n\t\t\tlv.onDoubleClick.Fire(NewEvent(lv, nil))\n\n\t\tcase w32.NM_CLICK:\n\t\t\tac := (*w32.NMITEMACTIVATE)(unsafe.Pointer(lparam))\n\t\t\tvar hti w32.LVHITTESTINFO\n\t\t\thti.Pt = w32.POINT{ac.PtAction.X, ac.PtAction.Y}\n\t\t\tw32.SendMessage(lv.hwnd, w32.LVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti)))\n\n\t\t\tif hti.Flags == w32.LVHT_ONITEMSTATEICON {\n\t\t\t\tif item := lv.findItemByIndex(int(hti.IItem)); item != nil {\n\t\t\t\t\tif item, ok := item.(ListItemChecker); ok {\n\t\t\t\t\t\tchecked := !item.Checked()\n\t\t\t\t\t\titem.SetChecked(checked)\n\t\t\t\t\t\tlv.onCheckChanged.Fire(NewEvent(lv, item))\n\n\t\t\t\t\t\tif w32.SendMessage(lv.hwnd, w32.LVM_UPDATE, uintptr(hti.IItem), 0) == w32.FALSE {\n\t\t\t\t\t\t\tpanic(\"SendMessage(LVM_UPDATE)\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\thti.Pt = w32.POINT{ac.PtAction.X, ac.PtAction.Y}\n\t\t\tw32.SendMessage(lv.hwnd, w32.LVM_SUBITEMHITTEST, 0, uintptr(unsafe.Pointer(&hti)))\n\t\t\tlv.onClick.Fire(NewEvent(lv, hti.ISubItem))\n\n\t\tcase w32.LVN_KEYDOWN:\n\t\t\tnmkey := (*w32.NMLVKEYDOWN)(unsafe.Pointer(lparam))\n\t\t\tif nmkey.WVKey == w32.VK_SPACE && lv.CheckBoxes() {\n\t\t\t\tif item := lv.SelectedItem(); item != nil {\n\t\t\t\t\tif item, ok := item.(ListItemChecker); ok {\n\t\t\t\t\t\tchecked := !item.Checked()\n\t\t\t\t\t\titem.SetChecked(checked)\n\t\t\t\t\t\tlv.onCheckChanged.Fire(NewEvent(lv, item))\n\t\t\t\t\t}","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v2/internal/frontend/desktop/windows/winc/listview.go#L466-L502","documentation":"In the list-view's WM_NOTIFY handler for NM_CLICK, when the click hits a state icon (LVHT_ONITEMSTATEICON) the code toggles the item's checked state and then sends LVM_UPDATE to repaint that item, panicking if the message returns FALSE. LVM_UPDATE fails when the item index is out of range or the control is being destroyed mid-notification. In this path the index comes from a fresh hit test, so the realistic trigger is teardown racing the click, or an item being removed concurrently so the hit-test index no longer exists.","triggerScenarios":"User clicks a checkbox in a ListView whose items are being mutated from another goroutine (delete/refresh between the hit test and LVM_UPDATE); the form is destroyed while the click notification is still processing; the hit-test returned LVHT_ONITEMSTATEICON with a stale/garbage IItem.","commonSituations":"ListViews refreshed from background data (polling, websocket pushes) while the user interacts; rapid close-during-click at shutdown.","solutions":["Mutate ListView items only on the UI thread (marshal updates with Form.Synchronize or PostMessage) so item indexes can't change under an in-flight notification","Batch refreshes: remove+reinsert atomically rather than deleting items while processing click notifications","On close, stop background updaters (cancel their context) before destroying the window so no click can race teardown"],"exampleFix":"// before\ngo func() {\n    for upd := range ch {\n        lv.RemoveItemByName(...) // background goroutine mutates items mid-click -> LVM_UPDATE FALSE -> panic\n    }\n}()\n\n// after\nfor upd := range ch {\n    upd := upd\n    form.Synchronize(func() { // serialize with the notification handler on the UI thread\n        lv.RemoveItemByName(...)\n    })\n}","handlingStrategy":"validation","validationCode":"// Serialize item mutations with the UI thread so click handlers never see stale indexes:\nfunc (f *MainForm) updateItems(fn func()) {\n    f.Synchronize(fn) // runs on the UI thread, after in-flight notifications complete\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Mutate ListView items only via the UI thread (Synchronize/PostMessage)","Cancel background refresh jobs before closing the window","Avoid removing items inside handlers that fire during click processing"],"tags":["windows","win32-api","listview","winc","panic","threading","lifecycle"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}