Click here to Skip to main content
15,887,266 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I ran into a weird issue with custom drawing the listview control. I drew the grid lines in response to CDDS_POSTPAINT notification. All working okay so far. However, when scrolling vertically, the lines will be messed up and will not be drawn at the correct positions unless some invalidation occurs after the scroll. Such as minimizing/restoring the window.

I've spent a while trying to figure out a solution to this issue. Nothing worked without introducing flickering.

I would really appreciate some hand over here.

Note: Code uses Common controls 6.0 through a manifest.

C++
#include <Windows.h>
#include <string>
#include <commctrl.h>

#pragma comment (lib,"Comctl32.lib")
#pragma comment (lib,"uxtheme.lib")

HWND g_hWndListView;

static HINSTANCE hInst;

LRESULT CALLBACK MainProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);

void InitListview(HWND hListview) {

    ListView_SetExtendedListViewStyle(hListview, LVS_EX_FULLROWSELECT
     | LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP);
    ListView_SetBkColor(hListview, RGB(32, 32, 32));
    ListView_SetTextBkColor(hListview, RGB(32, 32, 32));
    ListView_SetTextColor(hListview, RGB(240, 240, 240));

    // Hide focus dots
    SendMessage(hListview, WM_CHANGEUISTATE, 
                MAKELONG(UIS_SET, UISF_HIDEFOCUS), 0);

    static HWND hwndHeader = (HWND)SendMessageW
                             (hListview, LVM_GETHEADER, 0, 0);

    LVCOLUMN lvColumn;
    lvColumn.mask = LVCF_TEXT | LVCF_WIDTH;
    lvColumn.cx = 200;
    
    lvColumn.pszText = (LPWSTR)L"Header 1";
    SendMessage(hListview, LVM_INSERTCOLUMN, 0, (LPARAM)&lvColumn);

    lvColumn.pszText = (LPWSTR)L"Header 2";
    SendMessage(hListview, LVM_INSERTCOLUMN, 1, (LPARAM)&lvColumn);

    lvColumn.pszText = (LPWSTR)L"Header 3";
    SendMessage(hListview, LVM_INSERTCOLUMN, 2, (LPARAM)&lvColumn);

    LVITEM lvItem;
    lvItem.mask = LVIF_STATE | LVIF_TEXT;
    lvItem.state = LVIF_PARAM;
    lvItem.stateMask = NULL;

    for (int i = 0; i < 20; i++) {

        lvItem.iSubItem = 0;
        lvItem.iItem = i;
        lvItem.pszText = (LPWSTR)L"Item";
        SendMessage(hListview, LVM_INSERTITEM, 0, (LPARAM)&lvItem);
    }
}
void CreateListview(HWND hWnd) {


    HWND hListview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, 
                     L"", WS_TABSTOP | WS_CHILD | WS_VISIBLE | 
                     LVS_REPORT, 0, 0, 655, 160, hWnd, NULL, NULL, NULL);

    ::g_hWndListView = hListview;

    InitListview(hListview);
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPr, 
                   LPSTR args, int ncmdshow) {

    WNDCLASS WindowClass = { 0 };
    WindowClass.hbrBackground = CreateSolidBrush(0);
    WindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WindowClass.hInstance = hInst;
    //WindowClass.hIcon = LoadIcon(hInst, 0);
    WindowClass.lpszClassName = L"MainWindClass";
    WindowClass.lpfnWndProc = MainProc;  

    if (!RegisterClassW(&WindowClass)) {

        return -1;
    }

    int wCntr = GetSystemMetrics(SM_CXSCREEN) / 2;
    int hCntr = GetSystemMetrics(SM_CYSCREEN) / 2;

    HWND MainWindow = CreateWindowA("MainWindClass", "Main", 
    WS_OVERLAPPEDWINDOW | WS_VISIBLE, wCntr - (655 / 2), 
    hCntr - (200 / 2), 800, 400, NULL, NULL, NULL, NULL);

    MSG MainMsg = { 0 };
    while (GetMessage(&MainMsg, NULL, NULL, NULL)) {

        TranslateMessage(&MainMsg);
        DispatchMessage(&MainMsg);
    }

    return 0;
}

void ListView_DrawGridLines(HDC hDC,COLORREF GridColor)
{
    HWND hwndHeader;
    DWORD Counter = 0;
    DWORD ColumnsCount;
    DWORD ColumnWidth;
    RECT rectHeader;
    RECT rectLV;
    RECT ItemRect = { 0 };
    DWORD ItemHeight;
    int scrollHPos;
    COLORREF oldBkColor;
    RECT LineRect{ 0 };
    DWORD borderWidth;
    DWORD LineIndex;

    oldBkColor = SetBkColor(hDC, GridColor);
    hwndHeader = (HWND)SendMessage(g_hWndListView, LVM_GETHEADER, 0, 0);
    ColumnsCount = SendMessage(hwndHeader, HDM_GETITEMCOUNT, 0, 0);

    GetClientRect(g_hWndListView, &rectLV);
    GetClientRect(hwndHeader, &rectHeader);
    
    //Vertical lines
    scrollHPos = GetScrollPos(g_hWndListView, SB_HORZ) * -1;
    if (scrollHPos >= rectLV.right)
        return;
    
    borderWidth = GetSystemMetrics(SM_CXBORDER);

    while (Counter < ColumnsCount)
    {
        ColumnWidth = SendMessage(g_hWndListView, 
                                  LVM_GETCOLUMNWIDTH, Counter, 0);
        scrollHPos = scrollHPos + ColumnWidth;
        if (scrollHPos <= 0)
            break;

        LineRect.left = scrollHPos;
        LineRect.top = rectHeader.bottom;
        LineRect.right = scrollHPos + borderWidth;
        LineRect.bottom = rectLV.bottom;

        ExtTextOutW(hDC, 0, 0, ETO_OPAQUE, 
                    &LineRect, NULL, NULL, NULL);
        Counter++;
    }

    //Horizontal lines
    ItemRect.left = LVIR_BOUNDS;
    SendMessage(g_hWndListView, LVM_GETITEMRECT, 
                0,(LPARAM)&ItemRect);
    ItemHeight = ItemRect.bottom - ItemRect.top;

    LineIndex = rectHeader.bottom - 1;
    LineRect.bottom = LineIndex;

    while (LineIndex < rectLV.bottom)
    {
        LineIndex = LineIndex + borderWidth;
        LineRect.left = 0;
        LineRect.top = LineIndex - 1;
        LineRect.right = rectLV.right;
        LineRect.bottom = LineIndex;
        ExtTextOutW(hDC, 0, 0, ETO_OPAQUE, 
                    &LineRect, NULL, NULL, NULL);
        LineIndex = (LineRect.bottom - 1) + ItemHeight;
    }

    SetBkColor(hDC, oldBkColor);
}
int ListView_CustomDraw(LPNMLVCUSTOMDRAW lpnmlv)
{
    switch (lpnmlv->nmcd.dwDrawStage)
    {
    case CDDS_PREPAINT:
        return CDRF_NOTIFYPOSTPAINT;
        break;
  
    case CDDS_POSTPAINT:
        ListView_DrawGridLines(lpnmlv->nmcd.hdc, RGB(89, 89, 89));
        return CDRF_SKIPDEFAULT;
        break;
    default:
        return CDRF_DODEFAULT;
    }
}
LRESULT CALLBACK MainProc(HWND hWnd, UINT msg, 
                          WPARAM wParam, LPARAM lParam) {
    RECT rect;
    
    switch (msg) {

    case WM_CREATE: {

        CreateListview(hWnd);
        break;
    }
       
    case WM_NOTIFY:
    {
        LPNMHDR nmhdr = (LPNMHDR)lParam;

        if (nmhdr->code == NM_CUSTOMDRAW && 
            nmhdr->hwndFrom == g_hWndListView)
        {
            LPNMLVCUSTOMDRAW lpNMCustomDraw = 
                             (LPNMLVCUSTOMDRAW)lParam;
            return ListView_CustomDraw(lpNMCustomDraw);
        }       

        break;
    }

    case WM_SIZE:
    {
        GetClientRect(hWnd, &rect);
        SetWindowPos(g_hWndListView, NULL, 0, 0, 
                     rect.right - 2, rect.bottom - 2, 
                     SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
        break;
    }
    case WM_DESTROY: {

        PostQuitMessage(0);
        break;
    }
            
    default:

        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}


What I have tried:

I tried invalidating the listview rect in response to LVN_ENDSCROLL notification but that introduces flickering.

Tried using MoveToEx/LineTo APIs. Same issue. The horizontal lines disappear on scrolling vertically.
Posted
Updated 12-Sep-23 9:24am
v5

1 solution

See About Custom Draw - Win32 apps | Microsoft Learn[^] for custom drawing in Report mode.
 
Share this answer
 
Comments
NoviceCoder87 9-Sep-23 20:45pm    
@Richard MacCutchan

Thanks for the link. That doesn't explain the behavior I'm getting.

After wrestling with this issue for a while, I found out that if we have LVS_EX_GRIDLINES style set, this issue is completely fixed without the need to invalidate and therefore, no flickering. With that style set, one must ensure that the lines will be drawn exactly over the default ones. I don't like this solution as it seems hacky. Otherwise, without LVS_EX_GRIDLINES style, we must invalidate after LVN_ENDSCROLL notification and have some flickering.

Note that this issue only happens with scrolling with arrow icons of scroll/Max thumb click. Every other scrolling, like mouse wheel works fine.
I don't see other options here. Hopefully someone can come up with a better solution.
Richard MacCutchan 10-Sep-23 3:14am    
Yes, many of these problems only come to light when you do something slightly out of the ordinary. If you can demonstrate that it is a genuine problem with the class then you should report it to Microsoft.
NoviceCoder87 10-Sep-23 5:14am    
Yes, I agree. It's really shame that Microsoft hard-coded the grid lines color to COLOR_BTNFACE and left us with no control message to change their color. Simple stuff like that should really take no more than few seconds. I went with invalidating in response to LVN_ENDSCROLL notification and accepted control slight flickering.

Thanks for your contribution.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900