65.9K
CodeProject 正在变化。 阅读更多。
Home

ListView 交替行颜色(使用 Windows API)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.95/5 (12投票s)

2007年10月8日

CPOL

3分钟阅读

viewsIcon

62729

downloadIcon

2439

在 ListView(报表样式)控件中显示交替的行颜色。

Screenshot - AltRowCol.jpg

引言

我注意到有许多解决方案可以为 ListViewLVS_REPORT)显示交替行颜色,每个方案的成功程度各不相同。大多数似乎依赖于特定的编译器(Visual C++、C# 等)。这是一个简单的解决方案,仅使用 Windows API 调用,因此可以轻松转换为各种编译器和语言。

背景

代码依赖于您的编程语言能够拦截发送到 ListView 控件的 WM_PAINT WM_ERASEBKGND 消息。

让我们来看看涉及的过程:

首先,我们需要识别 ListView 控件的一些 API。

  • ListView_SetTextBkColor (HWND, COLORREF)

    设置文本背景颜色。请注意,此函数不会在新颜色中重绘整个控件背景,而只是设置后续重绘使用的颜色。

  • ListView_GetTopIndex (HWND)

    检索控件的第一个可见行的行索引。

  • ListView_GetCountPerPage (HNWD)

    检索可见行的数量。

  • ListView_GetItemPosition (HWND, int, RECT *)

    检索给定行的 POINT 坐标。

其次,我们需要一些函数来允许我们处理更新(或无效)矩形。

  • InvalidateRect (HWND, RECT *, BOOL)

    使控件的矩形区域无效,或标记为更新。

  • GetUpdateRect HWND, RECT *, BOOL)

    检索当前无效的更新矩形。

Using the Code

每当背景(客户端区域中未被列表列使用的部分)需要在屏幕上更新(或重绘)时,都会向控件发送 WM_ERASEBKGND 消息。

为了给人一种整个控件被分成不同颜色用于交替行的印象,需要根据此消息重绘背景。

我们使用一个循环,从第一个可见行到最后一个可见行(即使只是部分可见)遍历控件。对于每一行,根据该行是奇数行还是偶数行,使用相应的颜色(HBRUSH)填充一个矩形。

void EraseAlternatingRowBkgnds (HWND hWnd, HDC hDC)
//    re-draw row backgrounds with the appropriate background colour
{
    RECT    rect;        //    row rectangle
    POINT    pt;
    int     iItems,
            iTop;
    HBRUSH    brushCol1,    //    1st colour
            brushCol2;    //    2nd colour

//    create coloured brushes
    brushCol1 = CreateSolidBrush (GetSysColor (COLOR_WINDOW));
    brushCol2 = CreateSolidBrush (colorShade (GetSysColor (COLOR_WINDOW), 95.0));
//    get horizontal dimensions of row
    GetClientRect (hWnd, &rect);
//    number of displayed rows
    iItems = ListView_GetCountPerPage (hWnd);
//    first visible row
    iTop = ListView_GetTopIndex (hWnd);
    ListView_GetItemPosition (hWnd, iTop, &pt);

    for (int i=iTop ; i<=iTop+iItems ; i++) {
//        set row vertical dimensions
        rect.top = pt.y;
        ListView_GetItemPosition (hWnd, i+1, &pt);
        rect.bottom = pt.y;
//        fill row with appropriate colour
        FillRect (hDC, &rect, (i % 2) ? brushCol2 : brushCol1);
    }
    
//    cleanup
    DeleteObject (brushCol1);
    DeleteObject (brushCol2);
}  

当某个(矩形)区域需要在屏幕上更新时,会向控件发送 WM_PAINT 消息。例如,另一个窗口与控件重叠。与之前的过程类似,我们遍历可见行,但这次,我们仅更新与更新矩形相交的那些行。这是通过首先设置文本背景颜色,使行区域无效,然后调用 WM_PAINT 消息的默认操作来实现的。这会产生重绘行的效果,但使用我们指定的背景颜色。

void PaintAlternatingRows (HWND hWnd)
//    re-draw rows with the appropriate background colour
{
    RECT    rectUpd,        	//    rectangle to update
            rectDestin,        	//    temporary storage
            rect;            	//    row rectangle
    POINT    pt;
    int     iItems,
            iTop;
    COLORREF    c;            	//    temporary storage

//    get the rectangle to be updated
    GetUpdateRect (hWnd, &rectUpd, FALSE);
//    allow default processing first
    CallWindowProc (
        (FARPROC) PrevWndFunc, hWnd, WM_PAINT, 0, 0);
//    set the row horizontal dimensions
    SetRect (&rect, rectUpd.left, 0, rectUpd.right, 0);
//    number of displayed rows
    iItems = ListView_GetCountPerPage (hWnd);
//    first visible row
    iTop = ListView_GetTopIndex (hWnd);

    ListView_GetItemPosition (hWnd, iTop, &pt);
    for (int i=iTop ; i<=iTop+iItems ; i++) {
//        set row vertical dimensions
        rect.top = pt.y;
        ListView_GetItemPosition (hWnd, i+1, &pt);
        rect.bottom = pt.y;
//        if row rectangle intersects update rectangle then it requires
//        re-drawing
        if (IntersectRect (&rectDestin, &rectUpd, &rect)) {
//            change text background colour accordingly
            c = (i % 2) ? colorShade (GetSysColor (COLOR_WINDOW), 95.0) :
                GetSysColor (COLOR_WINDOW);
            ListView_SetTextBkColor (hWnd, c);
//            invalidate the row rectangle then...
            InvalidateRect (hWnd, &rect, FALSE);
//            ...force default processing
            CallWindowProc (
                (FARPROC) PrevWndFunc, hWnd, WM_PAINT, 0, 0);
        }
    }
}

函数 colorShade 用于提供备用颜色

COLORREF colorShade (COLORREF c, float fPercent)
//    create a lighter shade (by fPercent %) of a given colour
{
    return RGB ((BYTE) ((float) GetRValue (c) * fPercent / 100.0),
        (BYTE) ((float) GetGValue (c) * fPercent / 100.0),
        (BYTE) ((float) GetBValue (c) * fPercent / 100.0));
}

现在,为了让我们的代码正常工作,我们还有最后一件事要处理。

我们需要 链式 控件的默认 WndProc(窗口过程)与我们的例程,该例程

  1. 提供消息拦截,并且
  2. 为我们提供了强制执行默认操作的方法

我们借助 GetWindowLong SetWindowLong API 来实现这一点。

(其中 hWndListView ListView 控件的 HANDLE )

PrevWndFunc = (WNDPROC) GetWindowLong (hWndListView, GWL_WNDPROC); 

然后将默认 WndProc 函数设置为我们的 WndProc ListViewWndProc)。

SetWindowLong (hWndListView, GWL_WNDPROC, (LONG) ListViewWndProc); 

请注意全局变量(WNDPROC prevWndFunc),它存储默认的 WndProc 函数指针。

LRESULT CALLBACK ListViewWndProc (HWND hWnd, UINT iMessage, WPARAM wParam,
                    LPARAM lParam)
//    subclassed window procedure
{
    switch (iMessage) {
        case WM_PAINT:
//    intercept the WM_PAINT message which is called each time an area
//    of the control's client area requires re-drawing
            PaintAlternatingRows (hWnd);
            return 0;
        case WM_ERASEBKGND:
//    intercept the WM_ERASEBKGRN message which is called each time an area
//    of the control's client area background requires re-drawing
            EraseAlternatingRowBkgnds (hWnd, (HDC) wParam);
            return 0;
    }

// continue with default message processing
    return CallWindowProc (
        (FARPROC) PrevWndFunc, hWnd, iMessage, wParam, lParam);
} 

关注点

示例代码是一个完整的 Windows 程序(仅使用 API),它创建一个主窗口,其中包含一个 ListView 控件。示例使用的颜色是 COLOR_WINDOW (这是 ListView 控件的默认颜色)和 COLOR_WINDOW 的一个阴影(95%)。

此代码与 Borland C++(所有版本)配合良好,但应易于移植到 Visual C++ 等。

可以进一步采用相同的过程。例如,为不同的列设置不同的颜色,甚至是不同单元格的颜色组合。

历史

  • 2007 年 10 月 8 日:第一个版本
© . All rights reserved.