{"record":{"id":"bd3ed7b8164947dc","repo":"wailsapp/wails","slug":"sendmessage-lvm-setcallbackmask","errorCode":null,"errorMessage":"SendMessage(LVM_SETCALLBACKMASK)","messagePattern":"SendMessage\\(LVM_SETCALLBACKMASK\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v2/internal/frontend/desktop/windows/winc/listview.go","lineNumber":207,"sourceCode":"\toldStyle := exStyle\n\tif value {\n\t\texStyle |= w32.LVS_EX_CHECKBOXES\n\t} else {\n\t\texStyle &^= w32.LVS_EX_CHECKBOXES\n\t}\n\tif exStyle != oldStyle {\n\t\tw32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle)\n\t}\n\n\tmask := w32.SendMessage(lv.hwnd, w32.LVM_GETCALLBACKMASK, 0, 0)\n\tif value {\n\t\tmask |= w32.LVIS_STATEIMAGEMASK\n\t} else {\n\t\tmask &^= w32.LVIS_STATEIMAGEMASK\n\t}\n\n\tif w32.SendMessage(lv.hwnd, w32.LVM_SETCALLBACKMASK, mask, 0) == w32.FALSE {\n\t\tpanic(\"SendMessage(LVM_SETCALLBACKMASK)\")\n\t}\n}\n\nfunc (lv *ListView) applyImage(lc *w32.LVITEM, imIndex int) {\n\tif lv.iml != nil {\n\t\tlc.Mask |= w32.LVIF_IMAGE\n\t\tlc.IImage = int32(imIndex)\n\t}\n}\n\nfunc (lv *ListView) AddItem(item ListItem) {\n\tlv.InsertItem(item, lv.ItemCount())\n}\n\nfunc (lv *ListView) InsertItem(item ListItem, index int) {\n\ttext := item.Text()\n\tli := &w32.LVITEM{\n\t\tMask:    w32.LVIF_TEXT | w32.LVIF_PARAM,","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v2/internal/frontend/desktop/windows/winc/listview.go#L189-L225","documentation":"ListView's checkbox support (SetCheckBoxes -> the setCheckboxMask helper) reads the current callback mask with LVM_GETCALLBACKMASK, sets/clears LVIS_STATEIMAGEMASK, then sends LVM_SETCALLBACKMASK and panics if the message returns FALSE. The message fails when the HWND is not a list-view in a valid state — typically a destroyed window or a handle that never was a SysListView32 — because a live list-view accepts any callback mask.","triggerScenarios":"Calling listView.SetCheckBoxes(true) after the control's window was destroyed; calling it from a goroutine racing form teardown; invoking it on a control whose creation failed so hwnd is 0; sending LVM_* to a non-listview handle via misuse of the API.","commonSituations":"App shutdown paths that reconfigure UI (e.g. persisting preferences triggers SetCheckBoxes) while the window is closing; deferred configuration callbacks firing after close.","solutions":["Configure checkboxes once at setup, on the UI thread, while the form is guaranteed alive","Guard late/async calls with w32.IsWindow(lv.Handle()) before SetCheckBoxes","Cancel pending UI-mutating goroutines when the form closes (context/WaitGroup tied to WM_DESTROY)"],"exampleFix":"// before\nfunc (f *MainForm) onSettingsChange() {\n    f.list.SetCheckBoxes(true) // may run after window destroyed -> LVM_SETCALLBACKMASK FALSE -> panic\n}\n\n// after\nfunc (f *MainForm) onSettingsChange() {\n    if !w32.IsWindow(f.list.Handle()) { return }\n    f.list.SetCheckBoxes(true)\n}","handlingStrategy":"validation","validationCode":"func setCheckBoxesSafe(lv *winc.ListView, on bool) {\n    if lv == nil || lv.Handle() == 0 || !w32.IsWindow(lv.Handle()) { return }\n    lv.SetCheckBoxes(on)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Configure checkboxes once at control setup on the UI thread","Stop configuration goroutines when the form closes"],"tags":["windows","win32-api","listview","winc","panic","lifecycle"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}