CListCtrl是MFC中运用最广泛的控件之一,很多软件都有CListCtrl的身影,但是对于CListCtrl的自绘,很多朋友都犯了难,网上虽然有很多人讲解怎么自绘,但是实现出的效果都不是太友好,本篇讲解CListCtrl的Report自绘,对于LVS_ICON风格,我们采用窗口模拟进行绘制。先说一下Report重绘的注意事项。
![CListCtrl的Report风格自绘](https://img7.php1.cn/3cdc5/cf6c/78c/bd6fa7097919e4b9.jpeg)
![CListCtrl的Report风格自绘](https://img7.php1.cn/3cdc5/cf6c/78c/59dd78d813852c2b.jpeg)
转自http://jingyan.baidu.com/article/5bbb5a1b38af1113eaa17910.html 非常棒 感谢作者
CListCtrl是MFC中运用最广泛的控件之一,很多软件都有CListCtrl的身影,但是对于CListCtrl的自绘,很多朋友都犯了难,网上虽然有很多人讲解怎么自绘,但是实现出的效果都不是太友好,本篇讲解CListCtrl的Report自绘,对于LVS_ICON风格,我们采用窗口模拟进行绘制。先说一下Report重绘的注意事项。
CListCtrl控件分为上下两部分,上面的标头位置为标头控件,下方是我们需要绘制的CListCtrl,标头控件我们需要继承CHeaderCtrl进行标头控件的重绘,然后在CListCtrl中我们子类化CHeaderCtrl控件
CHeaderCtrl控件高度设置,有两种方法
1:通过SetFont强制撑大控件的高度
//创建字体
CFont Font;
Font.CreatePointFont(m_uItemHeight,TEXT("宋体"));
//设置字体
SetFont(&Font);
2:通过HDM_LAYOUT消息修改高度,添加ON_MESSAGE(HDM_LAYOUT, OnLayout)
LRESULT CSkinHeaderCtrl::OnLayout( WPARAM wParam, LPARAM lParam )
{
LRESULT lResult = CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam);
HD_LAYOUT &hdl = *( HD_LAYOUT * ) lParam;
RECT *prc = hdl.prc;
WINDOWPOS *pwpos = hdl.pwpos;
int nHeight = 28;
pwpos->cy = nHeight;
prc->top = nHeight;
return lResult;
}
上述代码中设置了控件的高度为28
重绘CListCtrl,先将Owner Draw Fixed设为true或者Create的时候添加LVS_OWNERDRAWFIXED属性,目的就是让控件能响应DrawItem从而实现自绘,此处需要注意,对于LVS_ICON风格,DrawItem不会被系统调用,不管是否添加LVS_OWNERDRAWFIXED属性,当然可以通过在WM_PAINT内进行绘制,不过这样会给Report带来诸多的麻烦,所以,我们通过模拟来实现LVS_ICON的绘制
CListCtrl高度的设置,因为CListCtrl并没有诸如SetItemHeight之类的函数进行节点高度的设置,所以我们必须自己实现这样的功能
void CSkinListCtrl::SetItemHeight( int nHeight )
{
m_nHeightItem = nHeight;
CRect rcWin;
GetWindowRect(&rcWin);
WINDOWPOS wp;
wp.hwnd = m_hWnd;
wp.cx = rcWin.Width();
wp.cy = rcWin.Height();
wp.flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
SendMessage(WM_WINDOWPOSCHANGED, 0, (LPARAM)&wp);
}
同时添加ON_WM_MEASUREITEM_REFLECT消息反射
void CSkinListCtrl::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )
{
if (m_nHeightItem>0)
{
lpMeasureItemStruct->itemHeight = m_nHeightItem;
}
}
这里提到了消息发射,注意区分和消息映射的区别,关于消息反射的概念不做赘述,可以通过MSDN进行查阅
CListCtrl为我们提供了SetExtendedStyle函数可以添加CListCtrl的很多扩展属性,这里强调一点LVS_EX_CHECKBOXES会导致ON_WM_MEASUREITEM_REFLECT消息反射不被接收,LVS_EX_GRIDLINES会造成节点矩形不准确,所以,我们需要重载屏蔽这些属性
DWORD CSkinListCtrl::SetExtendedStyle( DWORD dwNewStyle )
{
if ( dwNewStyle & LVS_EX_CHECKBOXES )
{
dwNewStyle &=~LVS_EX_CHECKBOXES;
dwNewStyle &=~LVS_EX_GRIDLINES;
}
return __super::SetExtendedStyle(dwNewStyle);
}
Check设置,因为我们上面屏蔽了LVS_EX_CHECKBOXES,所以默认情况下,当我们插入节点的时候,默认是被Check的,所以, 还需要重载四个InsertItem函数在里面默认将Check取消掉,例如
int CSkinListCtrl::InsertItem( const LVITEM* pItem )
{
int nResult = __super::InsertItem(pItem);
SetCheck(pItem->iItem,FALSE);
return nResult;
}
关于标头控件节点的拖动,当我们将鼠标放在两个节点的交叉处,鼠标会变成拖动的样式,此时按住左键可以拉伸节点的宽度,但是很多时候我们不想让前面的几个进行拖动,这个时候,我们需要在CHeaderCtrl派生类中重载OnChildNotify函数
BOOL CSkinHeaderCtrl::OnChildNotify(UINT uMessage, WPARAM wParam, LPARAM lParam, LRESULT * pLResult)
{
//变量定义
NMHEADER * pNMHearder=(NMHEADER*)lParam;
//拖动消息
if ((pNMHearder->hdr.code==HDN_BEGINTRACKA)||(pNMHearder->hdr.code==HDN_BEGINTRACKW))
{
//禁止拖动
if (pNMHearder->iItem<(INT)m_uLockCount)
{
*pLResult&#61;TRUE;
return TRUE;
}
}
return __super::OnChildNotify(uMessage,wParam,lParam,pLResult);
}
其中m_uLockCount就是禁止拖动的个数&#xff0c;比如设置为2&#xff0c;则前面2个节点将不能拉伸宽度&#xff0c;我们可以在写一个方法
//设置锁定
VOID CSkinHeaderCtrl::SetLockCount(UINT uLockCount)
{
//设置变量
m_uLockCount&#61;uLockCount;
return;
}
创建CHeaderCtrl的派生类CSkinHeaderCtrl&#xff0c;现在主要看一下OnPaint的绘制
//重画函数
VOID CSkinHeaderCtrl::OnPaint()
{
CPaintDC dc(this);
//获取位置
CRect rcRect;
GetClientRect(&rcRect);
//双缓冲&#xff0c;内存DC
CMemoryDC BufferDC(&dc,rcRect);
//设置 DC
BufferDC.SetBkMode(TRANSPARENT);
BufferDC.SetTextColor(m_colNormalText);
BufferDC.SelectObject(GetCtrlFont());
//绘画背景
if (m_pBackImg !&#61; NULL && !m_pBackImg->IsNull())
m_pBackImg->Draw(&BufferDC,rcRect);
if (m_pPressImg !&#61; NULL && !m_pPressImg->IsNull() && m_bPress)
{
CRect rcItem;
GetItemRect(m_uActiveItem,&rcItem);
m_pPressImg->Draw(&BufferDC,rcItem);
}
//绘画子项
CRect rcItem;
HDITEM HDItem;
TCHAR szBuffer[64];
for (INT i&#61;0;i { //构造变量 HDItem.mask&#61;HDI_TEXT; HDItem.pszText&#61;szBuffer; HDItem.cchTextMax&#61;CountArray(szBuffer); //获取信息 GetItem(i,&HDItem); GetItemRect(i,&rcItem); if (m_pGridImg !&#61; NULL && !m_pGridImg->IsNull()) m_pGridImg->DrawImage(&BufferDC,(rcItem.right-m_pGridImg->GetWidth()),(rcItem.Height()-m_pGridImg->GetHeight())/2); //绘画标题 rcItem.DeflateRect(3,1,3,1); BufferDC.DrawText(szBuffer,lstrlen(szBuffer),&rcItem,DT_END_ELLIPSIS|DT_SINGLELINE|DT_VCENTER|DT_CENTER); } return; }
鼠标按下
void CSkinHeaderCtrl::OnLButtonDown( UINT nFlags, CPoint point )
{
CRect rcItem;
for (INT i&#61;0;i { GetItemRect(i,&rcItem); if ( PtInRect(&rcItem,point) ) { m_bPress &#61; true; m_uActiveItem &#61; i; break; } } RedrawWindow(NULL,NULL,RDW_FRAME|RDW_INVALIDATE|RDW_ERASE|RDW_ERASENOW); __super::OnLButtonDown(nFlags, point); }
鼠标抬起
void CSkinHeaderCtrl::OnLButtonUp( UINT nFlags, CPoint point )
{
m_bPress &#61; false;
RedrawWindow(NULL,NULL,RDW_FRAME|RDW_INVALIDATE|RDW_ERASE|RDW_ERASENOW);
__super::OnLButtonUp(nFlags, point);
}
新建CListCtrl的派生类CSkinListCtrl
struct tagItemImage
{
CImageEx *pImage;
int nItem;
};
typedef vector
重点看一下绘制
//绘画函数
VOID CSkinListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
//变量定义
CRect rcItem&#61;lpDrawItemStruct->rcItem;
CDC * pDC&#61;CDC::FromHandle(lpDrawItemStruct->hDC);
CMemoryDC BufferDC(pDC,rcItem);
//获取属性
INT nItemID&#61;lpDrawItemStruct->itemID;
INT nColumnCount&#61;m_SkinHeaderCtrl.GetItemCount();
//绘画区域
CRect rcClipBox;
BufferDC.GetClipBox(&rcClipBox);
//设置环境
BufferDC.SetBkMode(TRANSPARENT);
BufferDC.SetTextColor(m_colNormalText);
BufferDC.SelectObject(GetCtrlFont());
BufferDC->FillSolidRect(&rcItem,m_colBack);
//绘画焦点
if (lpDrawItemStruct->itemState&ODS_SELECTED)
{
if (m_pSelectImg !&#61; NULL && !m_pSelectImg->IsNull())
m_pSelectImg->Draw(&BufferDC,rcItem);
}
else if ( m_uActiveItem &#61;&#61; nItemID )
{
if (m_pHovenImg !&#61; NULL && !m_pHovenImg->IsNull())
m_pHovenImg->Draw(&BufferDC,rcItem);
}
//绘画子项
for (INT i&#61;0;i { //获取位置 CRect rcSubItem; GetSubItemRect(nItemID,i,LVIR_BOUNDS,rcSubItem); //绘画判断 if (rcSubItem.left>rcClipBox.right) break; if (rcSubItem.right //绘画数据 DrawReportItem(&BufferDC,nItemID,rcSubItem,i); } return; }
//绘画数据
VOID CSkinListCtrl::DrawReportItem(CDC * pDC, INT nItem, CRect & rcSubItem, INT nColumnIndex)
{
//获取文字
TCHAR szString[256]&#61;TEXT("");
GetItemText(nItem,nColumnIndex,szString,CountArray(szString));
//绘画文字
rcSubItem.left&#43;&#61;5;
//绘制CheckButton
if( nColumnIndex &#61;&#61; 0 )
{
if ((m_pCheckImg !&#61; NULL && !m_pCheckImg->IsNull()) && (m_pUnCheckImg !&#61; NULL && !m_pUnCheckImg->IsNull()))
{
if( GetCheck(nItem) )
m_pCheckImg->DrawImage(pDC,rcSubItem.left&#43;2,rcSubItem.top&#43;(rcSubItem.Height()-m_pCheckImg->GetHeight())/2);
else
m_pUnCheckImg->DrawImage(pDC,rcSubItem.left&#43;2,rcSubItem.top&#43;(rcSubItem.Height()-m_pUnCheckImg->GetHeight())/2);
rcSubItem.left&#43;&#61;(8&#43;m_pCheckImg->GetWidth());
}
CItemImgArray::iterator iter &#61; m_ItemImgArray.begin();
for (;iter !&#61; m_ItemImgArray.end(); &#43;&#43;iter )
{
if ( iter->nItem &#61;&#61; nItem )
{
CImageEx *pImage &#61; iter->pImage;
if (pImage !&#61; NULL && !pImage->IsNull())
{
pImage->DrawImage(pDC,rcSubItem.left&#43;2,rcSubItem.top&#43;(rcSubItem.Height()-pImage->GetHeight())/2);
rcSubItem.left&#43;&#61;(8&#43;pImage->GetWidth());
}
break;
}
}
}
pDC->DrawText(szString,lstrlen(szString),&rcSubItem,DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS);
return;
}
这里说一下节点的图标&#xff0c;因为每个节点的图标都可能不一样&#xff0c;而且&#xff0c;节点的图标随时可能因为其他的需要发生更换&#xff0c;所以这里我们采用vector进行图标的管理&#xff0c;
BOOL CSkinListCtrl::InsertImage( int nItem,LPCTSTR lpszFileName )
{
CItemImgArray::iterator iter &#61; m_ItemImgArray.begin();
for (;iter !&#61; m_ItemImgArray.end(); &#43;&#43;iter )
{
if ( iter->nItem &#61;&#61; nItem )
{
//当该节点存在图片时&#xff0c;我们更新新的图片资源
if( iter->pImage !&#61; NULL )
{
RenderEngine->RemoveImage(iter->pImage);
iter->pImage &#61; RenderEngine->GetImage(lpszFileName);
return TRUE;
}
}
}
tagItemImage ItemImage;
//设置数据
ItemImage.nItem &#61; nItem;
ItemImage.pImage &#61; RenderEngine->GetImage(lpszFileName);
if (NULL &#61;&#61; ItemImage.pImage)
return FALSE;
else
{
m_ItemImgArray.push_back(ItemImage);
return TRUE;
}
}
void CSkinListCtrl::OnMouseMove( UINT nFlags, CPoint point )
{
CRect rcItem;
static UINT uOldActiveItem &#61; -1;
for (int i&#61;0;i { GetItemRect(i,rcItem,LVIR_BOUNDS); if ( PtInRect(&rcItem,point) ) { m_uActiveItem &#61; i; if( uOldActiveItem !&#61; m_uActiveItem ) { uOldActiveItem &#61; m_uActiveItem; Invalidate(FALSE); } break; } } __super::OnMouseMove(nFlags, point); } 这里注意&#xff0c;我们设置了一个静态变量来保存之前鼠标移动过的节点&#xff0c;此变量的意义很简单&#xff0c;因为无法保证用户是不是在同一个节点晃动鼠标&#xff0c;如果只要监听到鼠标移动消息就进行刷新&#xff0c;这样的话会消耗过多的cpu资源
void CSkinListCtrl::OnLButtonDown( UINT nFlags, CPoint point )
{
if (m_pCheckImg !&#61; NULL && !m_pCheckImg->IsNull())
{
CRect rcSubItem,rcIcon;
for (int i&#61;0;i { GetItemRect(i,rcSubItem,LVIR_BOUNDS); rcIcon.left &#61; rcSubItem.left&#43;7; rcIcon.top &#61; rcSubItem.top&#43;(rcSubItem.Height()-m_pCheckImg->GetHeight())/2; rcIcon.right &#61; rcIcon.left &#43; m_pCheckImg->GetWidth(); rcIcon.bottom &#61; rcIcon.top &#43; m_pCheckImg->GetHeight(); if ( PtInRect(&rcIcon,point) ) { SetCheck(i,!GetCheck(i)); SetItemState(i, LVIS_FOCUSED | LVIS_SELECTED,LVIS_FOCUSED | LVIS_SELECTED); SetSelectionMark(i); Invalidate(FALSE); break; } } } __super::OnLButtonDown(nFlags, point); }
关于Report的自绘就完成了&#xff0c;看一下调用过程
m_ListCtrl5.m_SkinHeaderCtrl.SetBackImage(TEXT("Res\\ListCtrl\\folder_nav_item_bg_hover.png"),&CRect(2,2,2,2));
m_ListCtrl5.m_SkinHeaderCtrl.SetPressImage(TEXT("Res\\ListCtrl\\folder_nav_item_bg_pressed.png"),&CRect(2,2,2,2));
m_ListCtrl5.m_SkinHeaderCtrl.SetGridImage(TEXT("Res\\ListCtrl\\category_sep.png"));
m_ListCtrl5.SetHovenImage(TEXT("Res\\ListCtrl\\item_bg_hover.png"),&CRect(2,2,2,2));
m_ListCtrl5.SetSelectImage(TEXT("Res\\ListCtrl\\item_bg_selected.png"),&CRect(2,2,2,2));
m_ListCtrl5.SetCheckImage(TEXT("Res\\ListCtrl\\check.png"),TEXT("Res\\ListCtrl\\uncheck.png"));
m_ListCtrl5.SetScrollImage(&m_ListCtrl5,TEXT("Res\\Scroll\\SKIN_SCROLL.bmp"));
m_ListCtrl5.InsertColumn( 0, TEXT("文件名"), LVCFMT_LEFT, 150 );
m_ListCtrl5.InsertColumn( 1, TEXT("大小"), LVCFMT_LEFT, 100 );
m_ListCtrl5.InsertColumn( 2, TEXT("修改时间"), LVCFMT_LEFT, 150 );
for (int i&#61;0;i<8;i&#43;&#43;)
{
m_ListCtrl5.InsertItem(i,TEXT("跟我学MFC.zip"));
m_ListCtrl5.SetItemText(i,1,TEXT("126MB"));
m_ListCtrl5.SetItemText(i,2,TEXT("2013-10-17 18:13"));
}
m_ListCtrl5.InsertImage(0,TEXT("Res\\ListCtrl\\DocType.png"));
m_ListCtrl5.InsertImage(1,TEXT("Res\\ListCtrl\\FolderType.png"));
m_ListCtrl5.InsertImage(2,TEXT("Res\\ListCtrl\\ImgType.png"));
m_ListCtrl5.InsertImage(3,TEXT("Res\\ListCtrl\\PdfType.png"));
m_ListCtrl5.InsertImage(4,TEXT("Res\\ListCtrl\\PptType.png"));
m_ListCtrl5.InsertImage(5,TEXT("Res\\ListCtrl\\RarType.png"));
//m_ListCtrl5.InsertImage(6,TEXT("Res\\ListCtrl\\VsdType.png"));
m_ListCtrl5.InsertImage(7,TEXT("Res\\ListCtrl\\VideoType.png"));
//更新资源图片
m_ListCtrl5.InsertImage(0,TEXT("Res\\ListCtrl\\VideoType.png"));
m_ListCtrl5.SetItemHeight(30);
//标头控件禁止拖动
//m_ListCtrl6.m_SkinHeaderCtrl.EnableWindow(FALSE);
//让第一个节点禁止拖动
m_ListCtrl6.m_SkinHeaderCtrl.SetLockCount(1);