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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (4投票s)

2009 年 6 月 22 日

CPOL

4分钟阅读

viewsIcon

55723

downloadIcon

3227

解决列表控件的标题“变形”及其位置、行高和文本格式的问题。

问题陈述与解决方法

我不知道是否能解释这样一个事实:Windows 标准 CListCtrl / SysListView32 列表的 CHeaderCtrl / SysHeader32 标题会出现一些一眼就能看出的变形。这影响了使用这些控件的程序的整体印象。通常,列表的行会比标题行落后 1 像素,而标题行则比应有的尺寸矮 2 像素。为了隐藏这种效果,通常会隐藏列表的行。如图 1-3 所示,这似乎不错。

image001.jpg

图 1. 带有数据和网格线的典型 CListCtrl 后代。

image003.jpg

图 2. 同一个 CListCtrl 后代,没有网格线。

image005.jpg

图 3. 带有数据和网格线的放大 CListCtrl 后代。

除了解决标题“变形”的问题外,我们还希望能够任意更改标题的高度以及列表控件的行高,而不管当前字体大小和列表中列或行中显示的图像图标。因此,我们希望以最优雅的方式解决这些问题。我们还希望在标题和列表单元格中使用预设字体和文本格式。除此之外,在演示示例中,对于典型列表,我们将通过 InsertItem / SetItem 方式普通地填充数据;对于修改后的列表,我们将使用虚拟模式 (LVS_OWNERDATA 样式),借助 SetItemCount 函数和 LVN_GETDISPINFO 消息。

性能如图 4 所示。我们注意到,尽管我们希望典型 CListCtrl 后代的第一个列“Ind. No.”在右边缘对齐,但该控件忽略了我们的“命令”,并在左边缘对齐了该列(图 1-3)。只有对于修改后的控件,我们才能够获得所需的对齐方式(图 4)。此外,我们还展示了可以更改数据格式。

image007.jpg

图 4. 修改后的 CHeaderCtrl / CListCtrl 后代,带有数据和网格线。

如果我们通过放大字体或图像图标来更改标题的高度,那么指示的“变形”仍然存在。显然,这是 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 日:初始发布
© . All rights reserved.