WinAPI WC_LISTVIEW绘制问题

时间:2016-12-08 07:09:24

标签: c++ listview winapi

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

我正在尝试创建平滑滚动。一切都很完美,除了列表没有正确绘制。请参阅下面的列表截图: List View Paint Problem

列表视图在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. 相同
  5. InvalidateRect + SendMessage(hwnd,WM_PAINT,0,0) - 与2相同

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

    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;
    }
    

3 个答案:

答案 0 :(得分:0)

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

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

度过美好的一天!

答案 1 :(得分:0)

从MSDN for ScrollWindowEx:

  

如果未指定SW_INVALIDATE和SW_ERASE标志,则ScrollWindowEx不会使滚动的区域无效。如果设置了其中任何一个标志,ScrollWindowEx将使该区域无效。在应用程序调用UpdateWindow函数,调用RedrawWindow函数(指定RDW_UPDATENOW或RDW_ERASENOW标志)或从应用程序队列中检索WM_PAINT消息之前,该区域不会更新。

所以,让我们来看看你的尝试:

  

UpdateWindow() - 附加截屏

没有任何内容失效,因此更新区域为空,因此UpdateWindow不执行任何操作。

  

RedrawWindow包含所有可能的选项 - 窗口仅绘制一次

如果调用正确,这应该使客户端无效。您可以立即使WM_ERASEBKGND发生,但只有当队列中没有其他内容时,WM_PAINT消息才会出现。我怀疑这没有用,因为WM_TIMER优先于WM_PAINT。 (两者都是特殊消息,因为它们实际上并未发布,但在您调用GetMessage时合成,而其他任何内容都未处理。)

  

InvalidateRect + UpdateWindow =与2

相同

我希望这可以工作,但是将标志传递给ScrollWindowEx以获得失效似乎更有意义。 我认为发生的事情是控件不是为了将项目绘制为非整体位置而设计的。所以你得到了失效,但窗口试图以不同的偏移量绘制项目。我没有看到解决这个问题的简单方法。

  

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

这不是有效的WM_PAINT消息。别这么做。

答案 2 :(得分:0)

问题是ListView是使用LVS_REPORT标志创建的。这意味着列表不能平滑滚动,只能在行中滚动。即例如,如果行高为25,则滚动20个像素将使列表滚动25个像素。

另一个问题是ScrollWindowEx并没有真正滚动列表(或者至少没有正确使用它)。为了滚动列表,应该使用ListView_Scroll宏(它再次按行而不是按像素滚动)。