c++ - WinAPI WC_LISTVIEW 绘制问题

标签 c++ listview winapi

我使用 CreateWindowEx 并使用 WC_LISTVIEW 作为类名创建了一个 ListView。

我正在尝试创建一个平滑的滚动。除了列表未正确绘制外,一切正常。请参阅下面的列表屏幕截图: List View Paint Problem

ListView 在CreateWindowEx中有如下样式:

WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER | 
        WS_TABSTOP | WS_BORDER | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_OWNERDRAWFIXED

我正在使用定时器来滚动窗口。它执行以下操作:

ScrollWindowEx(
    listHandle,
    0,
    step * linesDelta,
    NULL,
    NULL,
    0, 0, 0
    );
UpdateWindow(listHandle);

除了绘画之外,滚动效果完美。

我试过:

  1. UpdateWindow() - 附上截图

  2. 具有所有可能选项的 RedrawWindow - 窗口仅绘制一次

  3. InvalidateRect + UpdateWindow = 同 2

  4. InvalidateRect + SendMessage(hwnd, WM_PAINT, 0, 0) - 与 2 相同

为列表绘制项目的代码如下:

LRESULT drawItem(HWND hwnd, DRAWITEMSTRUCT* drawStruct) {

    Item *itemData = (Item *)drawStruct->itemData;
    HDC hdc = drawStruct->hDC;

    COLORREF backgroundColor;
    COLORREF oldColor;

    if (drawStruct->itemState & ODS_SELECTED || ListView_GetHotItem(hwnd) == drawStruct->itemID) {
        backgroundColor = GetSysColor(COLOR_HIGHLIGHT);
        oldColor = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
    } else {
        backgroundColor = RGB(255, 255, 255);
        oldColor = SetTextColor(hdc, GetSysColor(COLOR_CAPTIONTEXT));
    }

    HBRUSH backgroundBrush = CreateSolidBrush(backgroundColor);

    HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, backgroundBrush);
    FillRect(hdc, &drawStruct->rcItem, backgroundBrush);

    drawStruct->rcItem.left += 5;
    drawStruct->rcItem.right -= 5;

    drawStruct->rcItem.left += 30;
    DrawText(hdc, itemData->path, -1, &drawStruct->rcItem,
        DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS);
    drawStruct->rcItem.left -= 30;

    if (itemData->searchData && itemData->searchData->bitmap) {
        HBITMAP bitmap = itemData->searchData->bitmap;
        HDC hdcMem = CreateCompatibleDC(hdc);
        HGDIOBJ oldBitmap = SelectObject(hdcMem, bitmap);

        BITMAPINFO bi = { 0 };
        bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

        // Get the bitmap info header.
        if (0 != GetDIBits(
            hdcMem,   // hdc
            bitmap,  // hbmp
            0,          // uStartScan
            0,          // cScanLines
            NULL,       // lpvBits
            &bi,
            DIB_RGB_COLORS
            )) {

            BLENDFUNCTION blendFunc;
            blendFunc.BlendOp = AC_SRC_OVER;
            blendFunc.BlendFlags = 0;
            blendFunc.SourceConstantAlpha = 255;
            blendFunc.AlphaFormat = AC_SRC_ALPHA;

            AlphaBlend(hdc,
                drawStruct->rcItem.left + 2, //dest X
                drawStruct->rcItem.top + 3, //dest Y
                bi.bmiHeader.biWidth,
                bi.bmiHeader.biHeight,
                hdcMem, 0, 0,
                bi.bmiHeader.biWidth,
                bi.bmiHeader.biHeight, blendFunc);
        }

        SelectObject(hdcMem, oldBitmap);
        DeleteDC(hdcMem);
    }

    SelectObject(hdc, hOldBrush);
    DeleteObject(backgroundBrush);
    SetTextColor(hdc, oldColor);

    return 0;
}

有人知道解决这个问题的方法吗?

请在下面查看一个从头开始创建的具有完全相同行为的完整示例:

#include "stdafx.h"
#include "TestList.h"
#include <strsafe.h>
#include <commctrl.h>

#define MAX_LOADSTRING 100
#define ID_LIST_BOX 200

#define TIMER_ID_SMOOTH_SCROLL 100

class ListData {

    int scrollToDelta;

    int currentScrollPos;

    int numPixelsToChangeScrollPos;

    int numPixelsChanged;

public:

    HWND listWindow;

    WNDPROC defaultListProcedure;

    void startSmoothScrolling(HWND hwnd, int delta) {
        if (delta < 0) {
            scrollToDelta = 100;
        } else {
            scrollToDelta = -100;
        }

        SCROLLINFO si;
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_POS;

        if (GetScrollInfo(listWindow, SB_VERT, &si)) {
            double count = SendMessage(listWindow, LVM_GETITEMCOUNT, 0, 0);
            double totalHeight = count * 30;

            currentScrollPos = (int)((totalHeight * (double)si.nPos) / (double)si.nMax);
            numPixelsToChangeScrollPos = totalHeight / si.nMax;
            numPixelsChanged = 0;
        } else {
            currentScrollPos = 0;
            numPixelsChanged = 0;
            numPixelsToChangeScrollPos = 30;
        }

    }

    void smoothScroll(HWND listHandle) {

        SCROLLINFO si;
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_POS;

        DWORD linesDelta;
        SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesDelta, 0);

        if (scrollToDelta < 0) {
            if (GetScrollInfo(listHandle, SB_VERT, &si)) {
                if (si.nPos == 0) {
                    KillTimer(listHandle, TIMER_ID_SMOOTH_SCROLL);
                    return;
                }
            }

            scrollToDelta += 5;
            int step = -5;
            if (scrollToDelta > -80) {
                step = -4;
            } else if (scrollToDelta > -60) {
                step = -3;
            } else if (scrollToDelta > -40) {
                step = -3;
            } else if (scrollToDelta > -20) {
                step = -2;
            }

            numPixelsChanged += abs(step);
            if (numPixelsChanged >= numPixelsToChangeScrollPos) {
                int posDelta = numPixelsChanged / numPixelsToChangeScrollPos;
                numPixelsChanged -= posDelta * numPixelsToChangeScrollPos;
                si.nPos = si.nPos + posDelta;
                si.fMask = SIF_POS;
                SetScrollInfo(listHandle, SB_VERT, &si, TRUE);
            }

            ScrollWindowEx(
                listHandle,
                0,
                step * linesDelta,
                NULL,
                NULL,
                0, 0,
                SW_INVALIDATE);

            if (scrollToDelta >= 0) {
                KillTimer(listHandle, TIMER_ID_SMOOTH_SCROLL);
            }
        } else {

            if (GetScrollInfo(listHandle, SB_VERT, &si)) {
                int pos = GetScrollPos(listHandle, SB_VERT);
                if (pos == si.nMax) {
                    KillTimer(listHandle, TIMER_ID_SMOOTH_SCROLL);
                    return;
                }
            }

            scrollToDelta -= 5;
            int step = 5;
            if (scrollToDelta > -80) {
                step = 4;
            } else if (scrollToDelta > -60) {
                step = 3;
            } else if (scrollToDelta > -40) {
                step = 3;
            } else if (scrollToDelta > -20) {
                step = 2;
            }

            numPixelsChanged += abs(step);
            if (numPixelsChanged >= numPixelsToChangeScrollPos) {
                int posDelta = numPixelsChanged / numPixelsToChangeScrollPos;
                numPixelsChanged -= posDelta * numPixelsToChangeScrollPos;
                si.nPos = si.nPos - posDelta;
                si.fMask = SIF_POS;
                SetScrollInfo(listHandle, SB_VERT, &si, TRUE);
            }

            ScrollWindowEx(
                listHandle,
                0,
                step * linesDelta,
                NULL,
                NULL,
                0, 0, 0
                );

            if (scrollToDelta <= 0) {
                KillTimer(listHandle, TIMER_ID_SMOOTH_SCROLL);
            }
        }
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        //RedrawWindow(listHandle, NULL, NULL, 
        //  RDW_UPDATENOW | RDW_INTERNALPAINT | RDW_INVALIDATE | RDW_NOERASE | RDW_ALLCHILDREN | RDW_ERASENOW);
        //InvalidateRect(listHandle, NULL, FALSE);
        //SendMessage(listHandle, WM_PAINT, 0, 0);
        UpdateWindow(listHandle);
        //ListView_RedrawItems(listHandle, 0, 300);

        ////////////////////////////////////////////////////////////////////////////////////////////////////
    }

};

struct Item {
    WCHAR *name;
};

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.

    // Initialize global strings
    StringCchCopy(szTitle, MAX_LOADSTRING, L"Test");
    StringCchCopy(szWindowClass, MAX_LOADSTRING, L"TestClassList");
    MyRegisterClass(hInstance);

    // Perform application initialization:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTLIST));

    MSG msg;

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTLIST));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_TESTLIST);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

LRESULT drawItem(HWND hwnd, DRAWITEMSTRUCT* drawStruct) {

    Item *itemData = (Item *)drawStruct->itemData;
    HDC hdc = drawStruct->hDC;

    COLORREF backgroundColor;
    //pcd->clrTextBk;
    COLORREF oldColor;

    if (drawStruct->itemState & ODS_SELECTED || ListView_GetHotItem(hwnd) == drawStruct->itemID) {
        backgroundColor = GetSysColor(COLOR_HIGHLIGHT);
        oldColor = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
    } else {
        backgroundColor = RGB(255, 255, 255);
        oldColor = SetTextColor(hdc, GetSysColor(COLOR_CAPTIONTEXT));
    }

    HBRUSH backgroundBrush = CreateSolidBrush(backgroundColor);

    HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, backgroundBrush);
    FillRect(hdc, &drawStruct->rcItem, backgroundBrush);

    drawStruct->rcItem.left += 5;
    drawStruct->rcItem.right -= 5;

    drawStruct->rcItem.left += 30;
    DrawText(hdc, itemData->name, -1, &drawStruct->rcItem,
        DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS);
    drawStruct->rcItem.left -= 30;

    SelectObject(hdc, hOldBrush);
    DeleteObject(backgroundBrush);
    SetTextColor(hdc, oldColor);

    return 0;
}

LRESULT CALLBACK ListViewWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

    switch (uMsg) {
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        case WM_TIMER: {
            if (wParam == TIMER_ID_SMOOTH_SCROLL) {
                ListData *listData = (ListData*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
                listData->smoothScroll(hwnd);
            }
            break;
        }
        case WM_MOUSEWHEEL: {
            int delta = HIWORD(wParam);
            ListData *listData = (ListData*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
            listData->startSmoothScrolling(hwnd, delta);
            SetTimer(hwnd, TIMER_ID_SMOOTH_SCROLL, 200, NULL);
        }
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        default:
            ListData *listData = (ListData*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
            return CallWindowProc(listData->defaultListProcedure, hwnd, uMsg, wParam, lParam);
    }
    return 0;

}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      100, 100, 400, 400, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd) {
      return FALSE;
   }

   ////////////////////////////////////////////////////////////////////////////////////////////////////
   HWND listWindow = CreateWindowEx(
       0,
       WC_LISTVIEW,
       L"",
       WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER |
       WS_TABSTOP | WS_BORDER | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_OWNERDRAWFIXED,
       1,  //x
       1,  //y
       400 - 20, //width
       400 - 20, //height
       hWnd,
       (HMENU)ID_LIST_BOX,
       hInstance,
       NULL);

   ListData *listData = new ListData();
   listData->listWindow = listWindow;

   SetWindowLongPtr(listWindow, GWLP_USERDATA, (LPARAM)listData);
   listData->defaultListProcedure = (WNDPROC)SetWindowLongPtr(listWindow, GWLP_WNDPROC, (LONG_PTR)ListViewWindowProc);

   ListView_SetExtendedListViewStyle(listWindow, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_AUTOSIZECOLUMNS);
   SendMessage(listWindow, LVM_SETTEXTBKCOLOR, 0, 0xFFFFFF);

   LVCOLUMN col;

   col.mask = LVCF_TEXT | LVCF_WIDTH;
   col.pszText = L"";
   col.cx = 390;
   SendMessage(listWindow, LVM_INSERTCOLUMN, 0, (LPARAM)&col);

   LVITEM item;
   item.mask = LVIF_PARAM | LVIF_TEXT;
   item.iSubItem = 0;

   for (int i = 0; i < 300; i++) {
       item.iItem = i;
       Item *itemData = (Item*)malloc(sizeof(Item));
       WCHAR *name = (WCHAR*)malloc(sizeof(WCHAR) * 30);;
       wsprintf(name, L"Item Name %d", i);
       itemData->name = name;
       item.pszText = name;
       item.lParam = (LPARAM)itemData;
       SendMessage(listWindow, LVM_INSERTITEM, 0, (LPARAM)&item);
   }

   ////////////////////////////////////////////////////////////////////////////////////////////////////
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
        case WM_DRAWITEM: {
            ////////////////////////////////////////////////////////////////////////////////////////////////////
            if (wParam == ID_LIST_BOX) {
                DRAWITEMSTRUCT *drawStruct = (DRAWITEMSTRUCT*)lParam;
                drawItem(drawStruct->hwndItem, drawStruct);
                return TRUE;
            }
            ////////////////////////////////////////////////////////////////////////////////////////////////////
            break;
        }
    case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_MEASUREITEM: {
        if (wParam == ID_LIST_BOX) {
            MEASUREITEMSTRUCT *measureStruct = (MEASUREITEMSTRUCT*)lParam;
            measureStruct->itemHeight = 30;
            measureStruct->itemWidth = 390;
            return TRUE;
        }
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

最佳答案

这里有一个老技巧:在调用 ScrollWindowEx() 之前使整个 listHandle 区域无效而不删除背景。

InvalidateRect(listHandle, NULL, FALSE);
ScrollWindowEx(...

祝你有美好的一天!

关于c++ - WinAPI WC_LISTVIEW 绘制问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41033737/

相关文章:

c++ - 从编译器运行和作为独立 exe 运行时,进程访问权限似乎有所不同

C++ - Boost.Promise、Boost.Unique_Future 和 move 语义

C++ 字符串文字类型和声明

c++ - 初始化变量时遇到问题

android - 如果 ID 匹配或文件名匹配,搜索并单击按钮

android - 如何使抽屉导航中的特定项目显示为选中状态?

.net - 如何检测由于 Windows 7 幻灯片放映而导致的墙纸变化?

c++ - 如何删除回收站中路径太长的文件夹或文件?

c++ - fatal error : FlexLexer. h:没有那个文件或目录

android - 改变 ListView 行项目的颜色