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

引言
我注意到有许多解决方案可以为 ListView
(LVS_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
(窗口过程)与我们的例程,该例程
- 提供消息拦截,并且
- 为我们提供了强制执行默认操作的方法
我们借助 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 日:第一个版本