列表控件可以看作是功能增强的ListBox,它提供了四种风格,而且可以同时显示一列的多中属性值。MFC中使用CListCtrl类来封装列表控件的各种操作。通过调用 BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些列表控件的专用风格:
- LVS_ICON LVS_SMALLICON LVS_LIST LVS_REPORT 这四种风格决定控件的外观,同时只可以选择其中一种,分别对应:大图标显示,小图标显示,列表显示,详细报表显示
- LVS_EDITLABELS 结点的显示字符可以被编辑,对于报表风格来讲可编辑的只为第一列。
- LVS_SHOWSELALWAYS 在失去焦点时也显示当前选中的结点
- LVS_SINGLESEL 同时只能选中列表中一项
通过调用int InsertItem( int nItem, LPCTSTR lpszItem );可以在列表控件中nItem指明位置插入一项,lpszItem为显示字符。除LVS_REPORT风格外其他三种风格都只需要直接调用 InsertItem就可以了,但如果使用报表风格就必须先设置列表控件中的列信息。
通过调用int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat , int nWidth, int nSubItem);可以插入列。iCol为列的位置,从零开始,lpszColumnHeading为显示的列名,nFormat为显示对齐方式, nWidth为显示宽度,nSubItem为分配给该列的列索引。
在有多列的列表控件中就需要为每一项指明其在每一列中的显示字符,通过调用 BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );可以设置每列的显示字符。nItem为设置的项的位置,nSubItem为列位置,lpszText为显示字符。下面的代码演示了如何设置多列并插入数据:
m_list.SetImageList(&m_listSmall,LVSIL_SMALL);//设置ImageList
m_list.InsertColumn(0,"Col 1",LVCFMT_LEFT,300,0);//设置列
m_list.InsertColumn(1,"Col 2",LVCFMT_LEFT,300,1);
m_list.InsertColumn(2,"Col 3",LVCFMT_LEFT,300,2);
m_list.InsertItem(0,"Item 1_1");//插入行
m_list.SetItemText(0,1,"Item 1_2");//设置该行的不同列的显示字符
m_list.SetItemText(0,2,"Item 1_3");
此外CListCtrl还提供了一些函数用于得到/修改控件的状态。 COLORREF GetTextColor( )/BOOL SetTextColor( COLORREF cr );用于得到/设置显示的字符颜色。 COLORREF GetTextBkColor( )/BOOL SetTextBkColor( COLORREF cr );用于得到/设置显示的背景颜色。 void SetItemCount( int iCount );用于得到添加进列表中项的数量。 BOOL DeleteItem(int nItem);用于删除某一项,BOOL DeleteAllItems( );将删除所有项。 BOOL SetBkImage(HBITMAP hbm, BOOL fTile , int xOffsetPercent, int yOffsetPercent);用于设置背景位图。 CString GetItemText( int nItem, int nSubItem );用于得到某项的显示字符。
列表控件的消息映射同样使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXList(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于列表控件可能取值和对应的数据结构为:
- LVN_BEGINLABELEDIT 在开始某项编辑字符时发送,所用结构:NMLVDISPINFO
- LVN_ENDLABELEDIT 在结束某项编辑字符时发送,所用结构:NMLVDISPINFO
- LVN_GETDISPINFO 在需要得到某项信息时发送,(如得到某项的显示字符)所用结构:NMLVDISPINFO
关于动态提供结点所显示的字符:首先你在项时需要指明lpszItem参数为: LPSTR_TEXTCALLBACK。在控件显示该结点时会通过发送TVN_GETDISPINFO来取得所需要的字符,在处理该消息时先将参数 pNMHDR转换为LPNMLVDISPINFO,然后填充其中item.pszText。通过item中的iItem,iSubItem可以知道当前显示的为那一项。下面的代码演示了这种方法:
char szOut[8][3]={"No.1","No.2","No.3"};
//添加结点
m_list.InsertItem(LPSTR_TEXTCALLBACK,...)
m_list.InsertItem(LPSTR_TEXTCALLBACK,...)
//处理消息
void CParentWnd::OnGetDispInfoList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR;
pLVDI->item.pszText=szOut[pTVDI->item.iItem];//通过iItem得到需要显示的字符在数组中的位置
*pResult = 0;
}
关于编辑某项的显示字符:(在报表风格中只对第一列有效)首先需要设置列表控件的 LVS_EDITLABELS风格,在开始编辑时该控件将会发送LVN_BEGINLABELEDIT,你可以通过在处理函数中返回TRUE来取消接下来的编辑,在编辑完成后会发送LVN_ENDLABELEDIT,在处理该消息时需要将参数pNMHDR转换为LPNMLVDISPINFO,然后通过其中的item.pszText得到编辑后的字符,并重置显示字符。如果编辑在中途中取消该变量为NULL。下面的代码说明如何处理这些消息:
//处理消息 LVN_BEGINLABELEDIT
void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR;
if(pLVDI->item.iItem==0);//判断是否取消该操作
*pResult = 1;
else
*pResult = 0;
}
//处理消息 LVN_BEGINLABELEDIT
void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR;
if(pLVDI->item.pszText==NULL);//判断是否已经取消取消编辑
m_list.SetItemText(pLVDI->item.iItem,0,pLVDI->pszText);//重置显示字符
*pResult = 0;
}
上面讲述的方法所进行的消息映射必须在父窗口中进行(同样WM_NOTIFY的所有消息都需要在父窗口中处理)。
如何得到当前选中项位置:在列表控件中没有一个类似于ListBox中GetCurSel()的函数,但是可以通过调用GetNextItem( -1, LVNI_ALL | LVNI_SELECTED);得到选中项位置。
下面是一些例子
作者:lixiaosan 时间:04/06/2006
以下未经说明,listctrl默认view 风格为report
相关类及处理函数
MFC:CListCtrl类
SDK:以 “ListView_”开头的一些宏。如 ListView_InsertColumn
1. CListCtrl 风格
LVS_ICON: 为每个item显示大图标 LVS_SMALLICON: 为每个item显示小图标 LVS_LIST: 显示一列带有小图标的item LVS_REPORT: 显示item详细资料
直观的理解:windows资源管理器,“查看”标签下的“大图标,小图标,列表,详细资料”
2. 设置listctrl 风格及扩展风格
LONG lStyle; lStyle. = GetWindowLong(m_list.m_hWnd, GWL_STYLE);//获取当前窗口style lStyle. &= ~LVS_TYPEMASK; //清除显示方式位 lStyle.|= LVS_REPORT; //设置style SetWindowLong(m_list.m_hWnd, GWL_STYLE, lStyle);//设置style DWORD dwStyle. = m_list.GetExtendedStyle(); dwStyle.|= LVS_EX_FULLROWSELECT;//选中某行使整行高亮(只适用与report风格的listctrl) dwStyle.|= LVS_EX_GRIDLINES;//网格线(只适用与report风格的listctrl) dwStyle.|= LVS_EX_CHECKBOXES;//item前生成checkbox控件 m_list.SetExtendedStyle(dwStyle); //设置扩展风格 注:listview的style请查阅msdn http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceshellui5/html/wce50lrflistviewstyles.asp
3. 插入数据
m_list.InsertColumn( 0, "ID", LVCFMT_LEFT, 40 );//插入列 m_list.InsertColumn( 1, "NAME", LVCFMT_LEFT, 50 ); int nRow = m_list.InsertItem(0, “11”);//插入行 m_list.SetItemText(nRow, 1, “jacky”);//设置数据
4. 一直选中item
选中style中的Show selection always,或者在上面第2点中设置LVS_SHOWSELALWAYS5. 选中和取消选中一行
int nIndex = 0; //选中 m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED); //取消选中 m_list.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);
6. 得到listctrl中所有行的checkbox的状态
m_list.SetExtendedStyle(LVS_EX_CHECKBOXES); CString str; for(int i=0; i<m_list.GetItemCount(); i++) { if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED || m_list.GetCheck(i)) { str.Format(_T("第%d行的checkbox为选中状态"), i); AfxMessageBox(str); } }
7. 得到listctrl中所有选中行的序号
方法一: CString str; for(int i=0; i<m_list.GetItemCount(); i++) { if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED ) { str.Format(_T("选中了第%d行"), i); AfxMessageBox(str); } }
方法二: POSITION pos = m_list.GetFirstSelectedItemPosition(); if (pos == NULL) TRACE0("No items were selected!/n"); else { while (pos) { int nItem = m_list.GetNextSelectedItem(pos); TRACE1("Item %d was selected!/n", nItem); // you could do your own processing on nItem here } }
8. 得到item的信息
TCHAR szBuf[1024]; LVITEM lvi; lvi.iItem = nItemIndex; lvi.iSubItem = 0; lvi.mask = LVIF_TEXT; lvi.pszText = szBuf; lvi.cchTextMax = 1024; m_list.GetItem(&lvi);
关于得到设置item的状态,还可以参考msdn文章 Q173242: Use Masks to Set/Get Item States in CListCtrl http://support.microsoft.com/kb/173242/en-us
9. 得到listctrl的所有列的header字符串内容
LVCOLUMN lvcol; char str[256]; int nColNum; CString strColumnName[4];//假如有4列
nColNum = 0; lvcol.mask = LVCF_TEXT; lvcol.pszText = str; lvcol.cchTextMax = 256; while(m_list.GetColumn(nColNum, &lvcol)) { strColumnName[nColNum] = lvcol.pszText; nColNum++; }
10. 使listctrl中一项可见,即滚动滚动条
m_list.EnsureVisible(i, FALSE);11. 得到listctrl列数
int nHeadNum = m_list.GetHeaderCtrl()->GetItemCount();12. 删除所有列
方法一: while ( m_list.DeleteColumn (0)) 因为你删除了第一列后,后面的列会依次向上移动。
方法二: int nColumns = 4; for (int i=nColumns-1; i>=0; i--) m_list.DeleteColumn (i);
13. 得到单击的listctrl的行列号
添加listctrl控件的NM_CLICK消息相应函数 void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult) { // 方法一: /* DWORD dwPos = GetMessagePos(); CPoint point( LOWORD(dwPos), HIWORD(dwPos) ); m_list.ScreenToClient(&point); LVHITTESTINFO lvinfo; lvinfo.pt = point; lvinfo.flags = LVHT_ABOVE; int nItem = m_list.SubItemHitTest(&lvinfo); if(nItem != -1) { CString strtemp; strtemp.Format("单击的是第%d行第%d列", lvinfo.iItem, lvinfo.iSubItem); AfxMessageBox(strtemp); } */ // 方法二: /* NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; if(pNMListView->iItem != -1) { CString strtemp; strtemp.Format("单击的是第%d行第%d列", pNMListView->iItem, pNMListView->iSubItem); AfxMessageBox(strtemp); } */ *pResult = 0; }
14. 判断是否点击在listctrl的checkbox上
添加listctrl控件的NM_CLICK消息相应函数 void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult) { DWORD dwPos = GetMessagePos(); CPoint point( LOWORD(dwPos), HIWORD(dwPos) ); m_list.ScreenToClient(&point); LVHITTESTINFO lvinfo; lvinfo.pt = point; lvinfo.flags = LVHT_ABOVE; UINT nFlag; int nItem = m_list.HitTest(point, &nFlag); //判断是否点在checkbox上 if(nFlag == LVHT_ONITEMSTATEICON) { AfxMessageBox("点在listctrl的checkbox上"); } *pResult = 0; }
15. 右键点击listctrl的item弹出菜单
添加listctrl控件的NM_RCLICK消息相应函数 void CTest6Dlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; if(pNMListView->iItem != -1) { DWORD dwPos = GetMessagePos(); CPoint point( LOWORD(dwPos), HIWORD(dwPos) ); CMenu menu; VERIFY( menu.LoadMenu( IDR_MENU1 ) ); CMenu* popup = menu.GetSubMenu(0); ASSERT( popup != NULL ); popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this ); } *pResult = 0; }
16. item切换焦点时(包括用键盘和鼠标切换item时),状态的一些变化顺序
添加listctrl控件的LVN_ITEMCHANGED消息相应函数 void CTest6Dlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; // TODO: Add your control notification handler code here CString sTemp; if((pNMListView->uOldState & LVIS_FOCUSED) == LVIS_FOCUSED && (pNMListView->uNewState & LVIS_FOCUSED) == 0) { sTemp.Format("%d losted focus",pNMListView->iItem); } else if((pNMListView->uOldState & LVIS_FOCUSED) == 0 && (pNMListView->uNewState & LVIS_FOCUSED) == LVIS_FOCUSED) { sTemp.Format("%d got focus",pNMListView->iItem); } if((pNMListView->uOldState & LVIS_SELECTED) == LVIS_SELECTED && (pNMListView->uNewState & LVIS_SELECTED) == 0) { sTemp.Format("%d losted selected",pNMListView->iItem); } else if((pNMListView->uOldState & LVIS_SELECTED) == 0 && (pNMListView->uNewState & LVIS_SELECTED) == LVIS_SELECTED) { sTemp.Format("%d got selected",pNMListView->iItem); } *pResult = 0; }
17. 得到另一个进程里的listctrl控件的item内容
http://www.codeproject.com/threads/int64_memsteal.asp
18. 选中listview中的item
Q131284: How To Select a Listview Item Programmatically http://support.microsoft.com/kb/131284/en-us19. 如何在CListView中使用CListCtrl的派生类
http://www.codeguru.com/cpp/controls/listview/introduction/article.php/c919/
20. listctrl的subitem添加图标
m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES); m_list.SetItem(..); //具体参数请参考msdn
21. 在CListCtrl显示文件,并根据文件类型来显示图标
网上找到的代码,share BOOL CTest6Dlg::OnInitDialog() { CDialog::OnInitDialog(); HIMAGELIST himlSmall; HIMAGELIST himlLarge; SHFILEINFO sfi; char cSysDir[MAX_PATH]; CString strBuf; memset(cSysDir, 0, MAX_PATH); GetWindowsDirectory(cSysDir, MAX_PATH); strBuf = cSysDir; sprintf(cSysDir, "%s", strBuf.Left(strBuf.Find("//")+1)); himlSmall = (HIMAGELIST)SHGetFileInfo ((LPCSTR)cSysDir, 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON ); himlLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON); if (himlSmall && himlLarge) { ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST, (WPARAM)LVSIL_SMALL, (LPARAM)himlSmall); ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST, (WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge); } return TRUE; // return TRUE unless you set the focus to a control } void CTest6Dlg::AddFiles(LPCTSTR lpszFileName, BOOL bAddToDocument) { int nIcon = GetIconIndex(lpszFileName, FALSE, FALSE); CString strSize; CFileFind filefind; // get file size if (filefind.FindFile(lpszFileName)) { filefind.FindNextFile(); strSize.Format("%d", filefind.GetLength()); } else strSize = "0"; // split path and filename CString strFileName = lpszFileName; CString strPath; int nPos = strFileName.ReverseFind('//'); if (nPos != -1) { strPath = strFileName.Left(nPos); strFileName = strFileName.Mid(nPos + 1); } // insert to list int nItem = m_list.GetItemCount(); m_list.InsertItem(nItem, strFileName, nIcon); m_list.SetItemText(nItem, 1, strSize); m_list.SetItemText(nItem, 2, strFileName.Right(3)); m_list.SetItemText(nItem, 3, strPath); } int CTest6Dlg::GetIconIndex(LPCTSTR lpszPath, BOOL bIsDir, BOOL bSelected) { SHFILEINFO sfi; memset(&sfi, 0, sizeof(sfi)); if (bIsDir) { SHGetFileInfo(lpszPath, FILE_ATTRIBUTE_DIRECTORY, &sfi, sizeof(sfi), SHGFI_SMALLICON | SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES |(bSelected ? SHGFI_OPENICON : 0)); return sfi.iIcon; } else { SHGetFileInfo (lpszPath, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), SHGFI_SMALLICON | SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES | (bSelected ? SHGFI_OPENICON : 0)); return sfi.iIcon; } return -1; }
22.MFC的CListCtrl的排序功能
// 排序用的比较函数
//处理代码
-
m_pCtrl = &this->GetListCtrl ( ); //获得指针
-
//CListCtrl样式改变
-
LONG lStyle;
-
lStyle = GetWindowLong (m_pCtrl->m_hWnd, GWL_STYLE ); //获取当前窗口style
-
lStyle &= ~LVS_TYPEMASK; //清除显示方式
-
lStyle |= LVS_REPORT; //设置style为Report显示
-
SetWindowLong (m_pCtrl->m_hWnd, GWL_STYLE, lStyle );
-
//CListCtrl扩展样式改变
-
DWORD dwStyle;
-
dwStyle = m_pCtrl->GetStyle ( ); //取得样式
-
dwStyle |= LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT ; //添加样式
-
m_pCtrl->SetExtendedStyle (dwStyle ); //重新设置
需要的样式(根据自己的需要的样式选择) LVS_ICON: 为每个item显示大图标 LVS_SMALLICON: 为每个item显示小图标 LVS_LIST: 显示一列带有小图标的item LVS_REPORT: 显示item详细资料 LVS_EX_FULLROWSELECT: 表示选中某行使整行高亮(只适用与report风格的CListCtrl) LVS_EX_GRIDLINES: 表示显示网格线(只适用与report风格的CListCtrl) 24.添加CListCtrl的记录
-
//插入列
-
m_pCtrl->InsertColumn ( 1, "第一列",LVCFMT_CENTER, 47 ); //序号,标题,标题显示位置,列宽度
-
m_pCtrl->InsertColumn ( 2, "第二列",LVCFMT_CENTER, 100 );
-
//写入数据
-
int nRow = m_list. InsertItem ( 0, “无用字符串” ); //插入行
-
m_list. SetItemText (nRow, 1, “ 1111” ); //设置数据
-
m_list. SetItemText (nRow, 2, “ 2222” ); //设置数据
25.判断CListCtrl的哪行记录被选中了
-
int n = -1;
-
POSITION pos = m_pCtrl->GetFirstSelectedItemPosition ( ); //返回第一个选中的行位置
-
if (pos != NULL )
-
{
-
while (pos )
-
{
-
n = m_pCtrl->GetNextSelectedItem (pos ); //返回下一个选中的行数(返回值从0开始)
-
//做相应操作
-
}
-
}
26.响应单击CListCtrl事件
//响应CMyListView的=NUM_CLICK消息
-
//直接用VC类向导生成后,不用再次添加声明和消息映射
-
//添加函数声明
-
afx_msg void OnClick (NMHDR* pNMHDR, LRESULT* pResult );
-
//添加消息映射
-
ON_NOTIFY_REFLECT (NM_CLICK, OnClick )
-
void CMyListView:: OnClick (NMHDR* pNMHDR, LRESULT* pResult )
-
{
-
// TODO: Add your control notification handler code here
-
NM_LISTVIEW* pNMListView = (NM_LISTVIEW* )pNMHDR;
-
if (pNMListView->iItem != -1 )
-
{
-
/*
-
CString strtemp;
-
strtemp.Format("单击的是第%d行第%d列",
-
pNMListView->iItem, pNMListView->iSubItem);
-
AfxMessageBox(strtemp);
-
*/
-
//值从0开始计算,无数据区不相应时间
-
//相应操作
-
}
-
*pResult = 0;
-
}
27 CListCtrl,CListView与LVN_ITEMCHANGED消息
若要在CListCtrl中行发生改变时得到通知,可以映射LVN_ITEMCHANGED消息。
ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnItemchanged)
afx_msg void OnItemchanged(NMHDR* pNMHDR,LRESULT* pResult);
// 行选择改变 void CMyListCtrl::OnItemchanged(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; ...您要进行的操作... *pResult = 0; }
注意,LVN_ITEMCHANGED消息的产生有以下几种可能: 1、由选中到没选中的变化; 2、由没选中到选中的变化; 3、由选中一行到选中另外一行的变化; 4、使用CListCtrl::SetItem函数更改了行; 如何检测LVN_ITEMCHANGED消息是由那一变化产生的呢? NM_LISTVIEW结构成员变量uChanged和uNewState包含着这类信息,看如下代码:
void CRunListView::OnItemchanged(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; if(pNMListView->uChanged == LVIF_STATE) { if(pNMListView->uNewState) TRACE0("选择改变且有选中的行/r/n"); else TRACE0("选择改变且没有选中的行/r/n"); } else TRACE0("行改变(CListCtrl::SetItem)/r/n"); *pResult = 0; }
CListCtrl的Report风格自绘
- |
- 浏览:2113
- |
- 更新:2013-10-20 11:53
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=TRUE;
return TRUE;
}
}
return __super::OnChildNotify(uMessage,wParam,lParam,pLResult);
}
其中m_uLockCount就是禁止拖动的个数,比如设置为2,则前面2个节点将不能拉伸宽度,我们可以在写一个方法
//设置锁定
VOID CSkinHeaderCtrl::SetLockCount(UINT uLockCount)
{
//设置变量
m_uLockCount=uLockCount;
return;
}
END
绘制的步骤
-
创建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;
}
-
鼠标按下
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);
}
-
鼠标抬起
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);
}
-
新建CListCtrl的派生类CSkinListCtrl
struct tagItemImage
{
CImageEx *pImage;
int nItem;
};
typedef vector<tagItemImage> CItemImgArray;
重点看一下绘制
-
//绘画函数
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;
}
-
//绘画数据
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;
}
-
这里说一下节点的图标,因为每个节点的图标都可能不一样,而且,节点的图标随时可能因为其他的需要发生更换,所以这里我们采用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;
}
}
-
void 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资源
-
void 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);
}
-
关于Report的自绘就完成了,看一下调用过程
END
调用过程
-
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=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);
END
void CCStringDlg::InitialProgram()
{
InitialInfoList();
PersonInfo pi;
pi.strName = "JXT";
pi.strAge = "24";
pi.strAddress = "Wuhan";
for (int i = 0; i < 32; i++)
{
CString strNum;
strNum.Format("%d", i);
pi.strNumber = strNum;
InsertRecordToList(&pi);
}
}
void CCStringDlg::InitialInfoList()
{
LONG lStyle;
lStyle = GetWindowLong(m_listInfo.m_hWnd, GWL_STYLE);//获取当前窗口style
lStyle &= ~LVS_TYPEMASK; //清除显示方式位
lStyle |= LVS_REPORT; //设置style
SetWindowLong(m_listInfo.m_hWnd, GWL_STYLE, lStyle); //设置style
DWORD dwStyle = m_listInfo.GetExtendedStyle();
dwStyle |= LVS_EX_FULLROWSELECT; // 选中某行使整行高亮(只适用与report风格的listctrl)
dwStyle |= LVS_EX_GRIDLINES; // 网格线(只适用与report风格的listctrl)
dwStyle |= LVS_EX_CHECKBOXES; // item前生成checkbox控件
m_listInfo.SetExtendedStyle(dwStyle); // 设置扩展风格
CString strText;
strText.LoadString(IDS_LIST_INDEX);
m_listInfo.InsertColumn(INFO_INDEX_INDEX, strText, LVCFMT_LEFT, INFO_WIDTH_INDEX);
strText.LoadString(IDS_LIST_NAME);
m_listInfo.InsertColumn(INFO_INDEX_NAME, strText, LVCFMT_LEFT, INFO_WIDTH_NAME);
strText.LoadString(IDS_LIST_AGE);
m_listInfo.InsertColumn(INFO_INDEX_AGE, strText, LVCFMT_LEFT, INFO_WIDTH_AGE);
strText.LoadString(IDS_LIST_ADDRESS);
m_listInfo.InsertColumn(INFO_INDEX_ADDRESS, strText, LVCFMT_LEFT, INFO_WIDTH_ADDRESS);
strText.LoadString(IDS_LIST_NUMBER);
m_listInfo.InsertColumn(INFO_INDEX_NUMBER, strText, LVCFMT_LEFT, INFO_WIDTH_NUMBER);
}
void CCStringDlg::InsertRecordToList(PersonInfo *pInfo)
{
int iItem = m_listInfo.GetItemCount();
CString strIndex;
strIndex.Format("%d", iItem);
m_listInfo.InsertItem(iItem, strIndex);
m_listInfo.SetItemText(iItem, INFO_INDEX_NAME, pInfo->strName);
m_listInfo.SetItemText(iItem, INFO_INDEX_AGE, pInfo->strAge);
m_listInfo.SetItemText(iItem, INFO_INDEX_ADDRESS, pInfo->strAddress);
m_listInfo.SetItemText(iItem, INFO_INDEX_NUMBER, pInfo->strNumber);
}
void CCStringDlg::GetRecordFromList(int iItem, PersonInfo &Info)
{
Info.strName = m_listInfo.GetItemText(iItem, INFO_INDEX_NAME);
Info.strAge = m_listInfo.GetItemText(iItem, INFO_INDEX_AGE);
Info.strAddress = m_listInfo.GetItemText(iItem, INFO_INDEX_ADDRESS);
Info.strNumber = m_listInfo.GetItemText(iItem, INFO_INDEX_NUMBER);
}
void CCStringDlg::OnButtonDelete()
{
// TODO: Add your control notification handler code here
int iSelecetCount = m_listInfo.GetSelectedCount();
int iItem = -1;
for (int i = 0; i < iSelecetCount; i++)
{
iItem = m_listInfo.GetNextItem(iItem, LVNI_SELECTED);
m_listInfo.DeleteItem(iItem);
for (int j = iItem; j < m_listInfo.GetItemCount(); j++)
{
CString strIndex;
strIndex.Format("%d", j);
m_listInfo.SetItemText(j, INFO_INDEX_INDEX, strIndex);
}
}
}
void CCListCtrlDlg::OnButtonDelete()
{
// TODO: Add your control notification handler code here
// POSITION pos = m_listInfo.GetFirstSelectedItemPosition();
// if (pos == NULL)
// {
// TRACE0("No items were selected!\n");
// }
// else
// {
// while (pos)
// {
// int nItem = m_listInfo.GetNextSelectedItem(pos);
// TRACE1(_T("Item %d was selected!\n"), nItem + 1);
// m_listInfo.DeleteItem(nItem); // 不应用于删除 error
// }
// }
int iSelecetCount = m_listInfo.GetSelectedCount();
int iItem = -1;
for (int i = 0; i < iSelecetCount; i++)
{
iItem = m_listInfo.GetNextItem(iItem, LVNI_SELECTED);
m_listInfo.DeleteItem(iItem);
for (int j = iItem; j < m_listInfo.GetItemCount(); j++)
{
CString strIndex;
strIndex.Format("%4d", j + 1);
m_listInfo.SetItemText(j, PERSON_INFO_INDEX, strIndex);
}
}
}
void CCListCtrlDlg::OnClickListInfo(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
if (pNMListView->iItem != -1)
{
// CString strTmp;
// strTmp.Format(_T("单击的是第%d行第%d列"),
// pNMListView->iItem, pNMListView->iSubItem);
// AfxMessageBox(strTmp);
TRACE2(_T("The row %d col %d was selected!\n"),
pNMListView->iItem + 1, pNMListView->iSubItem + 1);
}
*pResult = 0;
}
m_ToolTip.Create(this);
m_ToolTip.Activate(TRUE);
CWnd* pWnd = GetWindow (GW_CHILD);
while (pWnd)
{
int nID = pWnd->GetDlgCtrlID ();
if (nID != -1)
{
CString strText;
GetDlgItem(nID)->GetWindowText(strText);
m_ToolTip.AddTool (pWnd, strText/*pWnd->GetDlgCtrlID()*/);
}
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
void CCListCtrlDlg::OnClickListInfo(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
if (pNMListView->iItem != -1)
{
// CString strTmp;
// strTmp.Format(_T("单击的是第%d行第%d列"),
// pNMListView->iItem, pNMListView->iSubItem);
// AfxMessageBox(strTmp);
TRACE2(_T("The row %d col %d was selected!\n"),
pNMListView->iItem + 1, pNMListView->iSubItem + 1);
CString strText = m_listInfo.GetItemText(pNMListView->iItem, pNMListView->iSubItem);
m_ToolTip.UpdateTipText(strText, &m_listInfo);
}
*pResult = 0;
}
void CCListCtrlDlg::OnButtonChange()
{
// TODO: Add your control notification handler code here
static int i = 0;
m_strTest.Format(_T("Fighting %d"), ++i);
m_ToolTip.UpdateTipText(m_strTest, &m_editTest);
UpdateData(FALSE);
}
//方案1:窗口锁定技术。
m_listInfo.LockWindowUpdate()
while()
{
//取出数据,插入到数据库中。
}
m_listInfo.UnlockWindowUpdate()
//程序在每次循环的时候不闪烁了,但是每个查询周期还是闪烁。
//方案2: 不知道是否用对,每个查询周期还是要闪烁一次
SetWindowRedraw(hwnd, FALSE);
//过程同上
SetWindowRedraw(hwnd, TRUE);