Search code examples
cwindowslistviewwinapiwin32gui

How to sync ListView checkbox with selection?(WIN32)


I'm writing a simple Win32 program that has a ListView with check box and multi-row selection enabled. : enter image description here

It seems like checkbox checks and row selections are two distinct behaviors. Is there a way to sync these two behaviors, that whenever a check box is checked that row will be selected and whenever a row selected the corresponding checkbox will be check?


Solution

  • "whenever a row is selected, the corresponding checkbox will be checked"

    Check WM_NOTIFY and LVIS_SELECTED flag to detect when user selects a row. And use ListView_SetCheckState to check the check-box:

    BOOL CALLBACK DialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        static HWND hListView;
        switch (msg)
        {
        case WM_INITDIALOG:
            hListView = GetDlgItem(hWnd, IDC_LIST1);
            break;
    
        case WM_NOTIFY:
        {
            NMHDR* header = (NMHDR*)lParam;
            NMLISTVIEW* nmlist = (NMLISTVIEW*)lParam;
            if (header && header->idFrom == IDC_LIST1 && header->code == LVN_ITEMCHANGED)
                if (nmlist->uNewState & LVIS_SELECTED)
                    ListView_SetCheckState(hListView, nmlist->iItem, 1);
            break;
        }
        ...
    }
    

    "whenever a check box is checked that row will be selected"

    Check WM_NOTIFY and LVIS_STATEIMAGEMASK flag to detect when checkbox is checked, then use ListView_SetItemState to select the row.

    Also this can lead to recursive calls, because we change the row in response to checkbox, and we change the checkbox in response to row selection. Use the busy variable to stop recursive calls.

    case WM_NOTIFY:
        if (lParam)
        {
            NMHDR* header = (NMHDR*)lParam;
            NMLISTVIEW* nmlist = (NMLISTVIEW*)lParam;
    
            //use `busy` as a flag to prevent recursive calls:
            static BOOL busy = FALSE;
            if (!busy && header->hwndFrom == hListView && header->code == LVN_ITEMCHANGED)
            {
                busy = TRUE;
                if (nmlist->uNewState & LVIS_SELECTED)
                {
                    //row has been selected => check the checkbox
                    ListView_SetCheckState(hListView, nmlist->iItem, 1);
                }
                else if (nmlist->uNewState & LVIS_STATEIMAGEMASK)
                {
                    //checkbox has been changed => select/unselect the row
                    BOOL checked = ListView_GetCheckState(hListView, nmlist->iItem);
                    ListView_SetItemState(hListView, nmlist->iItem,
                         checked ? LVIS_SELECTED : 0, LVIS_SELECTED);
                }
                busy = FALSE;
            }
        }
        break;