CListCtrl的Report风格自绘
原文链接: http://jingyan.baidu.com/article/5bbb5a1b38af1113eaa17910.html
CListCtrl是MFC中运用最广泛的控件之一,很多软件都有CListCtrl的身影,但是对于CListCtrl的自绘,很多朋友都犯了难,网上虽然有很多人讲解怎么自绘,但是实现出的效果都不是太友好,本篇讲解CListCtrl的Report自绘,对于LVS_ICON风格,我们采用窗口模拟进行绘制。先说一下Report重绘的注意事项。


方法/步骤
- 1CListCtrl控件分为上下两部分,上面的标头位置为标头控件,下方是我们需要绘制的CListCtrl,标头控件我们需要继承CHeaderCtrl进行标头控件的重绘,然后在CListCtrl中我们子类化CHeaderCtrl控件 
- 2CHeaderCtrl控件高度设置,有两种方法 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 
- 3重绘CListCtrl,先将Owner Draw Fixed设为true或者Create的时候添加LVS_OWNERDRAWFIXED属性,目的就是让控件能响应DrawItem从而实现自绘,此处需要注意,对于LVS_ICON风格,DrawItem不会被系统调用,不管是否添加LVS_OWNERDRAWFIXED属性,当然可以通过在WM_PAINT内进行绘制,不过这样会给Report带来诸多的麻烦,所以,我们通过模拟来实现LVS_ICON的绘制 
- 4CListCtrl高度的设置,因为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进行查阅 
- 5CListCtrl为我们提供了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); } 
- 6Check设置,因为我们上面屏蔽了LVS_EX_CHECKBOXES,所以默认情况下,当我们插入节点的时候,默认是被Check的,所以, 还需要重载四个InsertItem函数在里面默认将Check取消掉,例如 int CSkinListCtrl::InsertItem( const LVITEM* pItem ) { int nResult = __super::InsertItem(pItem); SetCheck(pItem->iItem,FALSE); return nResult; } 
- 7关于标头控件节点的拖动,当我们将鼠标放在两个节点的交叉处,鼠标会变成拖动的样式,此时按住左键可以拉伸节点的宽度,但是很多时候我们不想让前面的几个进行拖动,这个时候,我们需要在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=TRUE; return TRUE; } } return __super::OnChildNotify(uMessage,wParam,lParam,pLResult); } 其中m_uLockCount就是禁止拖动的个数,比如设置为2,则前面2个节点将不能拉伸宽度,我们可以在写一个方法 //设置锁定 VOID CSkinHeaderCtrl::SetLockCount(UINT uLockCount) { //设置变量 m_uLockCount=uLockCount; return; } END
绘制的步骤
- 1创建CHeaderCtrl的派生类CSkinHeaderCtrl,现在主要看一下OnPaint的绘制 //重画函数 VOID CSkinHeaderCtrl::OnPaint() { CPaintDC dc(this); //获取位置 CRect rcRect; GetClientRect(&rcRect); //双缓冲,内存DC CMemoryDC BufferDC(&dc,rcRect); //设置 DC BufferDC.SetBkMode(TRANSPARENT); BufferDC.SetTextColor(m_colNormalText); BufferDC.SelectObject(GetCtrlFont()); //绘画背景 if (m_pBackImg != NULL && !m_pBackImg->IsNull()) m_pBackImg->Draw(&BufferDC,rcRect); if (m_pPressImg != 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=0;i<GetItemCount();i++) { //构造变量 HDItem.mask=HDI_TEXT; HDItem.pszText=szBuffer; HDItem.cchTextMax=CountArray(szBuffer); //获取信息 GetItem(i,&HDItem); GetItemRect(i,&rcItem); if (m_pGridImg != 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; } 
- 2鼠标按下 void CSkinHeaderCtrl::OnLButtonDown( UINT nFlags, CPoint point ) { CRect rcItem; for (INT i=0;i<GetItemCount();i++) { GetItemRect(i,&rcItem); if ( PtInRect(&rcItem,point) ) { m_bPress = true; m_uActiveItem = i; break; } } RedrawWindow(NULL,NULL,RDW_FRAME|RDW_INVALIDATE|RDW_ERASE|RDW_ERASENOW); __super::OnLButtonDown(nFlags, point); } 
- 3鼠标抬起 void CSkinHeaderCtrl::OnLButtonUp( UINT nFlags, CPoint point ) { m_bPress = false; RedrawWindow(NULL,NULL,RDW_FRAME|RDW_INVALIDATE|RDW_ERASE|RDW_ERASENOW); __super::OnLButtonUp(nFlags, point); } 
- 4新建CListCtrl的派生类CSkinListCtrl struct tagItemImage { CImageEx *pImage; int nItem; }; typedef vector<tagItemImage> CItemImgArray; 重点看一下绘制 
- 5//绘画函数 VOID CSkinListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { //变量定义 CRect rcItem=lpDrawItemStruct->rcItem; CDC * pDC=CDC::FromHandle(lpDrawItemStruct->hDC); CMemoryDC BufferDC(pDC,rcItem); //获取属性 INT nItemID=lpDrawItemStruct->itemID; INT nColumnCount=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 != NULL && !m_pSelectImg->IsNull()) m_pSelectImg->Draw(&BufferDC,rcItem); } else if ( m_uActiveItem == nItemID ) { if (m_pHovenImg != NULL && !m_pHovenImg->IsNull()) m_pHovenImg->Draw(&BufferDC,rcItem); } //绘画子项 for (INT i=0;i<nColumnCount;i++) { //获取位置 CRect rcSubItem; GetSubItemRect(nItemID,i,LVIR_BOUNDS,rcSubItem); //绘画判断 if (rcSubItem.left>rcClipBox.right) break; if (rcSubItem.right<rcClipBox.left) continue; //绘画数据 DrawReportItem(&BufferDC,nItemID,rcSubItem,i); } return; } 
- 6//绘画数据 VOID CSkinListCtrl::DrawReportItem(CDC * pDC, INT nItem, CRect & rcSubItem, INT nColumnIndex) { //获取文字 TCHAR szString[256]=TEXT(""); GetItemText(nItem,nColumnIndex,szString,CountArray(szString)); //绘画文字 rcSubItem.left+=5; //绘制CheckButton if( nColumnIndex == 0 ) { if ((m_pCheckImg != NULL && !m_pCheckImg->IsNull()) && (m_pUnCheckImg != NULL && !m_pUnCheckImg->IsNull())) { if( GetCheck(nItem) ) m_pCheckImg->DrawImage(pDC,rcSubItem.left+2,rcSubItem.top+(rcSubItem.Height()-m_pCheckImg->GetHeight())/2); else m_pUnCheckImg->DrawImage(pDC,rcSubItem.left+2,rcSubItem.top+(rcSubItem.Height()-m_pUnCheckImg->GetHeight())/2); rcSubItem.left+=(8+m_pCheckImg->GetWidth()); } CItemImgArray::iterator iter = m_ItemImgArray.begin(); for (;iter != m_ItemImgArray.end(); ++iter ) { if ( iter->nItem == nItem ) { CImageEx *pImage = iter->pImage; if (pImage != NULL && !pImage->IsNull()) { pImage->DrawImage(pDC,rcSubItem.left+2,rcSubItem.top+(rcSubItem.Height()-pImage->GetHeight())/2); rcSubItem.left+=(8+pImage->GetWidth()); } break; } } } pDC->DrawText(szString,lstrlen(szString),&rcSubItem,DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS); return; } 
- 7这里说一下节点的图标,因为每个节点的图标都可能不一样,而且,节点的图标随时可能因为其他的需要发生更换,所以这里我们采用vector进行图标的管理, BOOL CSkinListCtrl::InsertImage( int nItem,LPCTSTR lpszFileName ) { CItemImgArray::iterator iter = m_ItemImgArray.begin(); for (;iter != m_ItemImgArray.end(); ++iter ) { if ( iter->nItem == nItem ) { //当该节点存在图片时,我们更新新的图片资源 if( iter->pImage != NULL ) { RenderEngine->RemoveImage(iter->pImage); iter->pImage = RenderEngine->GetImage(lpszFileName); return TRUE; } } } tagItemImage ItemImage; //设置数据 ItemImage.nItem = nItem; ItemImage.pImage = RenderEngine->GetImage(lpszFileName); if (NULL == ItemImage.pImage) return FALSE; else { m_ItemImgArray.push_back(ItemImage); return TRUE; } } 
- 8void CSkinListCtrl::OnMouseMove( UINT nFlags, CPoint point ) { CRect rcItem; static UINT uOldActiveItem = -1; for (int i=0;i<GetItemCount();i++) { GetItemRect(i,rcItem,LVIR_BOUNDS); if ( PtInRect(&rcItem,point) ) { m_uActiveItem = i; if( uOldActiveItem != m_uActiveItem ) { uOldActiveItem = m_uActiveItem; Invalidate(FALSE); } break; } } __super::OnMouseMove(nFlags, point); } 这里注意,我们设置了一个静态变量来保存之前鼠标移动过的节点,此变量的意义很简单,因为无法保证用户是不是在同一个节点晃动鼠标,如果只要监听到鼠标移动消息就进行刷新,这样的话会消耗过多的cpu资源 
- 9void CSkinListCtrl::OnLButtonDown( UINT nFlags, CPoint point ) { if (m_pCheckImg != NULL && !m_pCheckImg->IsNull()) { CRect rcSubItem,rcIcon; for (int i=0;i<GetItemCount();i++) { GetItemRect(i,rcSubItem,LVIR_BOUNDS); rcIcon.left = rcSubItem.left+7; rcIcon.top = rcSubItem.top+(rcSubItem.Height()-m_pCheckImg->GetHeight())/2; rcIcon.right = rcIcon.left + m_pCheckImg->GetWidth(); rcIcon.bottom = rcIcon.top + 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); } 
- 10关于Report的自绘就完成了,看一下调用过程 END
调用过程
- 1m_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=0;i<8;i++) { 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); 
CListCtrl的Report风格自绘的更多相关文章
- [转载]CTreeCtrl 和 CListCtrl 控件(VC_MFC)
		来源:http://www.cnblogs.com/kzloser/archive/2012/11/23/2783305.html Tree Control 控件(MSDN 链接) 说明: 树形控件是 ... 
- 关于ListCtrol自绘的技巧
		一.给控件添加排序功能report风格的list控件很多情况下都需要支持排序功能,而且最好支持按不同列进行排序.CListCtrl的类方法SortItems支持排序功能,但是在排序过程中,两个数据真正 ... 
- MFC实现 自适应操作系统的CListCtrl控件
		新建对话框应用程序,删除自动生成的控件后,拖拽一个CListCtrl控件,绑定变量名为:m_listctrl.在对话框初始化成员函数OnInitDialog()中键入以下代码即可实现自适应系统的CLi ... 
- CListCtrl使用方法汇总
		回顾: 刚刚写完,因为是分期写的,所以最初想好好做一下的文章格式半途而废了~说的也许会有点啰嗦,但是所有的基础用到的技术细节应该都用到了. 如果还有什么疑问,请回复留言,我会尽力解答. 如果有错误,请 ... 
- MFC中CListCtrl说明
		转载:http://blog.csdn.NET/lhy2199/article/details/5177032 listctrl默认view 风格为report CListCtrl类封装"列 ... 
- CTreeCtrl和CListCtrl失去焦点时高亮选中项
		设置CTreeCtrl的Always Show Selection:TrueCListCtrl的Always Show Selection:False在NM_CUSTOMDRAW事件中添加如下代码: ... 
- CListCtrl使用(转)
		CListCtrl使用技巧 以下未经说明,listctrl默认view 风格为report 1. CListCtrl 风格 LVS_ICON: 为每个item显示大图标 LVS_SMALLI ... 
- 【转】MFC CListCtrl 使用技巧
		以下未经说明,listctrl默认view 风格为report 相关类及处理函数 MFC:CListCtrl类 SDK:以 “ListView_”开头的一些宏.如 ListView_InsertCol ... 
- CListCtrl控件使用方法总结
		今天第一次用CListCtrl控件,遇到不少问题,查了许多资料,现将用到的一些东西总结如下: 以下未经说明,listctrl默认view 风格为report 相关类及处理函数 MFC:CListCtr ... 
随机推荐
- 牛客网-《剑指offer》-跳台阶
			题目:http://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4 C++ class Solution { public: in ... 
- bash和shell的关系
			bash是borne again shell的缩写,它是shell的一种,Linux上默认采用的是bash. shell脚本中的方法带不带function的区别,例如: function foo () ... 
- vcenter SSO
- SQL Server从BAK文件还原新的数据库
			同一个数据库多个副本 很多时候,比如为了方便测试,排查问题,我们常常会拿到问题系统的数据库备份来开发环境下debug,这个时候就会出现同一个数据库的多个副本. 还原法 还原到一个新建的空数据库,在选项 ... 
- C# delegate (1)
			delegate是委托,本身不能解决跨线程访问控件的问题,直接调用委托还是会报错 Invoke指定用主线程中的控件去调用这个委托,相当于主线程来执行这个函数 在codeProject网站中找到的. C ... 
- Statement、PreparedStatemnt、CallableStatement
			第一.Statement(Statement代表一个特定的容器,来对一个特定的数据库执行语句) * 执行查询的方法 Statement=Connection.createStatement();//创 ... 
- JS密码校验规则前台验证(不能连续字符(如123、abc)连续3位或3位以上)(不能相同字符(如111、aaa)连续3位或3位以上)
			密码必须为8到16位且必须包含数字和字母 密码必须包含特殊字符[_&#%] 不能连续字符(如123.abc)连续3位或3位以上 不能相同字符(如111.aaa)连续3位或3位以上 /** * ... 
- MySQL 主从错误
			1: Got fatal error 1236 from master when reading data from binary log: 'Could not find first log fil ... 
- 〖Linux〗ADT_Bundle配置
			1. 配置自动补全: Windows -> preferences -> 搜索assist,修改 java xml自动触发补全:.abcdefghijklmnopqrstuvwxyzABC ... 
- php Socket表单提交学习一下
			<?php //发送请求指定的页面 $file = "1.php"; $filename = "gitignore.txt"; //文件名 $path = ... 
