2016-12-08 216 views
2

我使用CreateWindowEx創建了一個ListView並使用WC_LISTVIEW作爲類名。WinAPI 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() - screenshoot附

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

你永遠不會發送WM_PAINT所以扔掉這個想法。我們沒有太多的代碼。也許在代碼中存在一個我們看不到的問題。 –

+2

包含重現問題的完整示例。 – Andrei

+0

爲什麼你要重新實施「平滑滾動」輪?如果用戶需要,他們可以在系統級別打開它。而那些*也不能保持控制。 –

回答

0

這裏是一個老把戲:調用ScrollWindowEx()之前不擦除背景無效整個listHandle區域。

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

祝您有美好的一天!

0

從MSDN的ScrollWindowEx:

如果未指定SW_INVALIDATE和SW_ERASE標誌,ScrollWindowEx不壞是從滾動的區域。如果設置了這些標誌中的任何一個,則ScrollWindowEx會使該區域失效。在應用程序調用UpdateWindow函數,調用RedrawWindow函數(指定RDW_UPDATENOW或RDW_ERASENOW標誌)或從應用程序隊列中檢索WM_PAINT消息之前,該區域不會更新。

因此,讓我們來看看你試了一下:

UpdateWindow() - screenshoot連接

奈何得了無效,所以更新區域是空的,那麼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信息。不要這樣做。

0

問題是ListView是使用LVS_REPORT標誌創建的。這意味着列表不能順利滾動,但只能行。即如果行高例如是25,則滾動20個像素將使列表滾動25個像素。

還有一個問題是ScrollWindowEx沒有真正滾動列表(或者至少沒有正確使用)。爲了滾動列表,應該使用ListView_Scroll宏(它再次按行而不是像素滾動)。