【问题标题】:WinAPI WC_LISTVIEW paint issuesWinAPI WC_LISTVIEW 绘制问题
【发布时间】:2017-04-23 08:34:27
【问题描述】:

我使用 CreateWindowEx 创建了一个 ListView,并使用 WC_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;
}

【问题讨论】:

  • 你永远不会发送 WM_PAINT,所以放弃这个想法。我们没有太多代码。也许我们看不到的代码有问题。
  • 包含一个重现问题的完整示例。
  • 为什么要重新实现“平滑滚动”滚轮?如果用户想要这个,他们可以在系统级别打开它。而那些没有的人也可以保持控制。
  • Win 7 是否提供此选项?
  • 我认为问题在于ScrollWindowEx 只是滚动显示的位图,然后要求窗口重新绘制暴露区域。但是列表视图本身并没有滚动——它认为的“顶部索引”没有改变——所以它只是再次重新绘制旧项目。

标签: c++ listview winapi


【解决方案1】:

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

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

祝你有美好的一天!

【讨论】:

    【解决方案2】:

    来自 MSDN 的 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 消息。不要那样做。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-09
        • 1970-01-01
        • 2010-10-07
        • 2017-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多