更改 CHeaderCrtrl 的高度和位置以及 CListCtrl 后代的行高,并使用 HDM_LAYOUT 和 WM_MEASUREITEM 消息






4.60/5 (4投票s)
解决列表控件的标题“变形”及其位置、行高和文本格式的问题。
问题陈述与解决方法
我不知道是否能解释这样一个事实:Windows 标准 CListCtrl / SysListView32 列表的 CHeaderCtrl / SysHeader32 标题会出现一些一眼就能看出的变形。这影响了使用这些控件的程序的整体印象。通常,列表的行会比标题行落后 1 像素,而标题行则比应有的尺寸矮 2 像素。为了隐藏这种效果,通常会隐藏列表的行。如图 1-3 所示,这似乎不错。
除了解决标题“变形”的问题外,我们还希望能够任意更改标题的高度以及列表控件的行高,而不管当前字体大小和列表中列或行中显示的图像图标。因此,我们希望以最优雅的方式解决这些问题。我们还希望在标题和列表单元格中使用预设字体和文本格式。除此之外,在演示示例中,对于典型列表,我们将通过 InsertItem
/ SetItem
方式普通地填充数据;对于修改后的列表,我们将使用虚拟模式 (LVS_OWNERDATA
样式),借助 SetItemCount
函数和 LVN_GETDISPINFO
消息。
性能如图 4 所示。我们注意到,尽管我们希望典型 CListCtrl
后代的第一个列“Ind. No.”在右边缘对齐,但该控件忽略了我们的“命令”,并在左边缘对齐了该列(图 1-3)。只有对于修改后的控件,我们才能够获得所需的对齐方式(图 4)。此外,我们还展示了可以更改数据格式。
如果我们通过放大字体或图像图标来更改标题的高度,那么指示的“变形”仍然存在。显然,这是 Windows 控件 SysListView32
的“属性”,而不是 MFC 的 CListCtrl
类。
要消除标题的“变形”,需要额外将其向右移动 1 像素,并将其高度增加 2 像素。最好不要明显地调用 MoveWindow
/ SetWindowPos
等函数,也不需要像 OnPaint
这样的函数来自己绘制 CHeaderCtrl
/ CListCtrl
的后代。此外,我们不想非必要地使用 SetFont
函数(仅用于放大标题,然后调整其字体大小)。同样的情况也适用于图像图标,我们希望仅在必要时使用它们,而不是仅仅为了更改标题或行的大小(例如,通过使用细图标来决定高度,并且与背景区分不开)。还有一个变通方法是基于 CHeaderCtrl::Create
函数重新创建标题,该函数需要明确指定大小和位置,但这个方法也太臃肿了。
因此,我们有一个方法可以通过 HDM_LAYOUT
消息更改标题的高度和位置,还有一个方法可以通过 WM_MEASUREITEM
消息实际更改列表行的高度。HDM_LAYOUT
方法之所以有趣,还因为它经常被程序员错误地使用,有时会导致代码冗余或程序工作不正确。我们将展示如何格式化列表单元格及其标题中的文本。
HDM_LAYOUT 消息
要获取和处理表格标题的 HDM_LAYOUT
消息,** 必须在 HeaderCtrlEx.h 头文件中定义该函数
afx_msg LRESULT OnLayout(WPARAM wparam, LPARAM lparam)
然后在相应的 HeaderCtrlEx.cpp 文件中,在消息映射中指定一个宏
ON_MESSAGE(HDM_LAYOUT, OnLayout)
并定义相应的 OnLayout
函数
/////////////////////////////////////////////////////////
// OnLayout
/////////////////////////////////////////////////////////
LRESULT CHeaderCtrlEx::OnLayout(WPARAM, LPARAM lParam) {
LPHDLAYOUT pHL = reinterpret_cast<LPHDLAYOUT>(lParam);
//*** The table list rectangle
RECT *pRect = pHL->prc;
//*** The table header rectangle
WINDOWPOS *pWPos = pHL->pwpos;
//*** Here's equivalent code for the code which follows after
/*
pWPos->hwndInsertAfter = NULL;
//*** Moves the table header to the right
pWPos->x = pRect->left + m_nHdrWidthDefect;
pWPos->y = pRect->top;
pWPos->cx = pRect->right - pRect->left;
//*** New table header height
pWPos->cy = m_nHdrHeight + m_nHdrHeightDefect;
pWPos->flags = SWP_NOACTIVATE|SWP_NOZORDER;
//*** Decreases the table list height on the table header height
pRect->top += m_nHdrHeight;
return TRUE;
*/
//*** Sends HDM_LAYOUT message to the base class
int nRet = CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam);
//*** Moves the table header to the right
pWPos->x += m_nHdrWidthDefect;
//*** New table header height
pWPos->cy = m_nHdrHeight + m_nHdrHeightDefect;
//*** Decreases the table list height on the table header height
pRect->top = m_nHdrHeight;
return nRet;
} // OnLayout
实际上,这里提供了两种更改 CHeaderCtrlEx
类标题的大小和位置的方法,而无需显式使用 SetFont
/ MoveWindow
/ SetWindowPos
/ CHeaderCtrlEx::Create
等函数,或使用伪图像图标(以达到相同的目的)。尽管我们的 CHeaderCtrlEx::OnLayout
函数看起来很简单,但许多程序员在处理 HDM_LAYOUT
消息时都会出错。这可能是因为 MSDN 中对 LPHDLAYOUT
结构体的描述不够清晰。
WM_MEASUREITEM 消息
与前一个类似,我们在 ListCtrlEx.h 头文件中定义函数
afx_msg void MeasureItem(LPMEASUREITEMSTRUCT pMIS)
然后在相应的 ListCtrlEx.cpp 文件中,在消息映射中指定一个宏
ON_WM_MEASUREITEM_REFLECT()
并定义相应的 MeasureItem
函数
/////////////////////////////////////////////////////////////////////////////
// MeasureItem
/////////////////////////////////////////////////////////////////////////////
void CListCtrlEx::MeasureItem(LPMEASUREITEMSTRUCT pMIS) {
//*** The table list height
pMIS->itemHeight = m_nListHeight;
} // MeasureItem
文本格式化
根据我们的意愿确定文本格式化稍微困难一些。这是 CHeaderCtrlEx::DrawItem
的代码
/////////////////////////////////////////////////////////////////////////////
// DrawItem
/////////////////////////////////////////////////////////////////////////////
void CHeaderCtrlEx::DrawItem(LPDRAWITEMSTRUCT pDIS) {
HDITEM hDI;
TCHAR szBuf[MAXITEMTEXT];
hDI.mask = HDI_TEXT;
hDI.pszText = szBuf;
hDI.cchTextMax = MAXITEMTEXT;
GetItem(pDIS->itemID, &hDI);
CDC *pDC;
HDC hDC = pDIS->hDC; // Handle to device context
pDC = CDC::FromHandle(hDC);
//*** Selects necessary font
pDC->SelectObject(m_pFont);
//pDC->SelectObject(GetStockObject(DEFAULT_GUI_FONT));
int x = 0; // x-coordinate of reference point
int y = 0; // y-coordinate of reference point
UINT nOptions = 0; // Text-output options ETO_CLIPPED|ETO_OPAQUE
RECT *pIRect = NULL; // Optional clipping and/or opaquing rectangle
pIRect = &pDIS->rcItem;
SIZE Size = {0};
//*** Gets the header cell sizes
if(!GetTextExtentPoint(hDC, szBuf, wcslen(szBuf), &Size)) {
_M("Failed to call GetTextExtentPoint for table header!");
return;
}
// x-coordinate of reference point
x = (pIRect->left + pIRect->right - Size.cx)/2 - 1;
x = (x < pIRect->left + 2) ? pIRect->left + 2 : x;
// y-coordinate of reference point
y = (pIRect->bottom - pIRect->top - Size.cy)/2 - 1;
// Specifies that the current background color fills the rectangle pIRect
nOptions |= ETO_CLIPPED;
//*** Decreases the text border to the right
pIRect->right -= 4;
//*** Writes the text in the (x, y) - coordinates
pDC->ExtTextOut(x, y, nOptions, pIRect, szBuf, wcslen(szBuf), NULL);
//*** Restores system font
pDC->SelectStockObject(SYSTEM_FONT);
} // DrawItem
这是用于列表文本格式化的最实质性的代码
/////////////////////////////////////////////////////////////////////////////
// SetColItemText
/////////////////////////////////////////////////////////////////////////////
void CListCtrlEx::SetColItemText(CDC *pDC, CString& stColText, CRect& TextRect,
UINT nJustify)
{
int x = 0; // x-coordinate of reference point
int y = 0; // y-coordinate of reference point
UINT nOptions = 0; // Text-output options ETO_CLIPPED|ETO_OPAQUE
int nTextLen = stColText.GetLength();
HDC hDC = pDC->m_hDC;
SIZE Size = {0};
if(!GetTextExtentPoint(hDC, stColText, nTextLen, &Size)) {
_M("Failed to call GetTextExtentPoint for table list!");
return;
}
// Align the text in the whole table
CRect TmpRect(TextRect);
// x-coordinate of reference point
x = (TextRect.left + TextRect.right - Size.cx)/2 - 1;
x = (x < TextRect.left + 2) ? TextRect.left + 2 : x;
// y-coordinate of reference point
y = (TextRect.bottom - TextRect.top - Size.cy)/2 - 1;
//*** Specifies that the current background color fills the rectangle
nOptions |= ETO_OPAQUE;
//*** Draw the background fast
pDC->ExtTextOut(TextRect.left, TextRect.top, nOptions, TextRect, NULL, 0, NULL);
TmpRect.left++; // Cosmetic
TmpRect.top += y; // y-coordinate of reference point
TmpRect.InflateRect(-3, 0); // Text does not touch borders
UINT nFormat = 0;
switch(nJustify & LVCFMT_JUSTIFYMASK) {
case LVCFMT_LEFT:
nFormat = DT_LEFT;
break;
case LVCFMT_RIGHT:
nFormat = DT_RIGHT;
break;
case LVCFMT_CENTER:
nFormat = DT_CENTER;
break;
default:
_M("CListCtrlEx: Error of the text formatting!");
return;
}
//*** Writes the text in the TmpRect
::DrawText(hDC, stColText, nTextLen, TmpRect, nFormat);
} // SetColItemText
列表的虚拟模式的实现示例是众所周知的,因此我将不再赘述。
历史
- 2009 年 6 月 22 日:初始发布