MFC中的CListCtrl实际上是由两个控件组成,一个是表头控件,一个是列表控件。有些时候,我们需要重绘表头,使其满足特定的场景要求。
本文介绍了CListCtrl表头的重绘方法,自定义表头效果如下。
MFC中表示表头控件的类是CHeaderCtrl,我们从它派生一个新类CCustomHeader 。
头文件为:
class CCustomHeader : public CHeaderCtrl
{
DECLARE_DYNAMIC(CCustomHeader)
public:
CCustomHeader();
virtual ~CCustomHeader();
//设置表头单元格的对齐方式,参见DrawText函数中文字对齐格式
void SetTextAlign(UINT uFormat = DT_CENTER | DT_SINGLELINE | DT_VCENTER);
protected:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
DECLARE_MESSAGE_MAP()
private:
UINT m_nTextAlignFormat; //表头文字对齐方式
};
实现文件
// CCustomHeader.cpp: 实现文件
//
#include "pch.h"
#include "TestCustomHeader.h"
#include "CCustomHeader.h"
// CCustomHeader
IMPLEMENT_DYNAMIC(CCustomHeader, CHeaderCtrl)
CCustomHeader::CCustomHeader()
:m_nTextAlignFormat(DT_CENTER | DT_SINGLELINE | DT_VCENTER)
{}
CCustomHeader::~CCustomHeader()
{}
void CCustomHeader::SetTextAlign(UINT uFormat)
{
m_nTextAlignFormat = uFormat;
}
BEGIN_MESSAGE_MAP(CCustomHeader, CHeaderCtrl)
END_MESSAGE_MAP()
void CCustomHeader::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
ASSERT(lpDrawItemStruct->CtlType == ODT_HEADER);
HDITEM hdi;
TCHAR lpBuffer[256];
hdi.mask = HDI_TEXT;
hdi.pszText = lpBuffer;
hdi.cchTextMax = 256;
GetItem(lpDrawItemStruct->itemID, &hdi);
CDC* pDC;
pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
HGDIOBJ hOldFont = pDC->SelectObject(GetStockObject(DEFAULT_GUI_FONT));
// 绘制按钮边框
::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, DFC_BUTTON, DFCS_BUTTONPUSH);
//绘制文本
::DrawText(lpDrawItemStruct->hDC, lpBuffer, _tcslen(lpBuffer),
&lpDrawItemStruct->rcItem, m_nTextAlignFormat);
pDC->SelectObject(hOldFont);
}
请注意,在实现文件中,我们重写了DrawItem虚函数;因此当表头控件是自绘样式时,每次绘制都会调用DrawItem函数,在该函数中我们获取表头控件的DC,然后即可进行绘制。
另请注意在DrawItem中使用的如下函数:
BOOL GetItem( int nPos, HDITEM* pHeaderItem ) const;
说明:该函数可以获取表头控件每一项的信息(Retrieves information about a header control item.)
利用该函数,我们获取了表头中每一项的标题内容。
新建一个基于对话框的程序,在对话框类的头文件中增加:
CListCtrl m_wndLstMain; //表示列表控件本身
CCustomHeader m_wndHeader; //表示列表控件的表头控件
在OnInitDialog函数中增加:
BOOL CTestDlg::OnInitDialog()
{
//.... 省略
// TODO: 在此添加额外的初始化代码
CRect rect;
m_wndLstMain.GetClientRect(rect);
m_wndLstMain.InsertColumn(0, _T("姓名"), LVCFMT_LEFT, rect.Width() / 3);
m_wndLstMain.InsertColumn(1, _T("班级"), LVCFMT_LEFT, rect.Width() / 3);
m_wndLstMain.InsertColumn(2, _T("学号"), LVCFMT_LEFT, rect.Width() / 3);
m_wndLstMain.InsertItem(0, _T("张三"));
m_wndLstMain.SetItemText(0, 1, _T("一班"));
m_wndLstMain.SetItemText(0, 2, _T("001"));
m_wndLstMain.InsertItem(1, _T("李四"));
m_wndLstMain.SetItemText(1, 1, _T("一班"));
m_wndLstMain.SetItemText(1, 2, _T("002"));
m_wndLstMain.InsertItem(2, _T("王五"));
m_wndLstMain.SetItemText(2, 1, _T("一班"));
m_wndLstMain.SetItemText(2, 2, _T("003"));
m_wndLstMain.SetExtendedStyle(LVS_EX_GRIDLINES);
//子类化列表控件的表头
CHeaderCtrl* pHeader = m_wndLstMain.GetHeaderCtrl();
if(pHeader)
m_wndHeader.SubclassWindow(pHeader->GetSafeHwnd());
HDITEM hdItem;
hdItem.mask = HDI_FORMAT;
for (int i = 0; i < m_wndHeader.GetItemCount(); i++)
{
m_wndHeader.GetItem(i, &hdItem);
hdItem.fmt |= HDF_OWNERDRAW; //增加自绘样式
m_wndHeader.SetItem(i, &hdItem);
}
//.... 省略
}
注意在上述函数中,我们首先初始化了CListCtrl对象,创建了多个列及多个子项,然后利用GetHeaderCtrl()函数找到了CListCtrl对象的表头控件,最后调用SubclassWindow子类化表头控件并将其绑定到m_wndHeader(这是我们重写的表头控件类对象)。
但是,仅仅如此还并未结束,我们需要将表头的每一项都改为自绘样式,这样,表头重绘时才会调用DrawItem函数,具体代码为:
HDITEM hdItem;
hdItem.mask = HDI_FORMAT;
for (int i = 0; i < m_wndHeader.GetItemCount(); i++)
{
m_wndHeader.GetItem(i, &hdItem);
hdItem.fmt |= HDF_OWNERDRAW; //增加自绘样式
m_wndHeader.SetItem(i, &hdItem);
}