c++listboxredrawownerdrawn

Best way to tell owner draw listbox redraw an item?


I created an owner draw listbox and bind texts vector to it like below.

vector<wchar_t*> texts;

lbHWND = CreateWindowExW(NULL, WC_LISTBOX, NULL,
        WS_CHILD | WS_BORDER | WS_VISIBLE | LBS_NODATA | 
        LBS_OWNERDRAWFIXED | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,
        0, 0, 400, 400, tkHWND, (HMENU)IDC_LISTBOX_ENTRY, hInstance, 0);

SCROLLINFO lbSi = { 0 };
lbSi.cbSize = sizeof(SCROLLINFO);
lbSi.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
lbSi.nMin = 0;
lbSi.nMax = text.size();
lbSi.nPage = 20;
lbSi.nPos = 0;
SetScrollInfo(lbHWND, SB_VERT, &lbSi, TRUE);

SendMessageW(lbHWND, LB_RESETCONTENT, 0, 0);
SendMessage(lbHWND, LB_SETCOUNT, iTotal, 0);

LRESULT CALLBACK WndProc(HWND phwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_MEASUREITEM:
        {
            MEASUREITEMSTRUCT*  lpmis = (LPMEASUREITEMSTRUCT)lParam;
            switch (lpmis->CtlID)
            {
                case IDC_LISTBOX_ENTRY:
                    lpmis->itemHeight = 20;
                    break;
                default:
                    break;
            }
            return TRUE;
        }
        case WM_DRAWITEM:
        {
            DRAWITEMSTRUCT* lpdis = (LPDRAWITEMSTRUCT)lParam;

            if (lpdis->itemID == -1) return;
            HBRUSH hb = NULL;
            HPEN hp = NULL;

            switch (lpdis->itemAction) 
            { 
                case ODA_SELECT: 
                case ODA_DRAWENTIRE: 
                    SetBkMode(lpdis->hDC, TRANSPARENT);
                    hb = CreateSolidBrush(lpdis->itemState & ODS_SELECTED ? 0xf1f1f1 : 0xffffff);
                    hp = CreatePen(PS_SOLID, 1, lpdis->itemState & ODS_SELECTED ? 0xcfcfcf : 0xffffff);
                    SelectObject(lpdis->hDC, hp);
                    SelectObject(lpdis->hDC, hb);

                    Rectangle(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, lpdis->rcItem.right, lpdis->rcItem.bottom);

                    TextOut(lpdis->hDC,
                        lpdis->rcItem.left + 5,
                        lpdis->rcItem.top + 2,
                        texts[i],
                        wcslen(texts[i]);
                    break;
            }
            if (hp) DeleteObject(hp);
            if (hb) DeleteObject(hb);
        }
    }
}

Now suppose I update an item in texts, can you tell me a good way to tell the listbox redraw that item?

Currently, I use following code:

texts[2] = L"Some text";
SendMessageW(lbHWND, LB_RESETCONTENT, 0, 0);
SendMessage(lbHWND, LB_SETCOUNT, iTotal, 0);

which force listbox to redraw all items, not only the third item. Is there a better way to do this?


Solution

  • You can use LB_GETITEMRECT to retrieve the coordinates of the item and then InvalidateRect() to force a redraw of that particular region of the window:

    RECT r = {};
    if (SendMessage(lbHWND, LB_GETITEMRECT, index, &r) != LB_ERR)
        InvalidateRect(lbHWND, &r, 0);