本篇重点介绍:禁止拖动表头、让第一列居中显示、设置行高与字体、虚拟列表技术、点击表头时进行归类、向上与向下移动、动态调整大小问题、避免闪烁问题。
当数据量大时,使用InsertItem插入数据的过程是很漫长的。这时我们有两个方法来解决该问题:一是使用CListCtrl的虚拟列表技术,二是采用分页显示的方法。对于虚拟列表技术,上述链接中的文章讲的很详细,我用过它的比较简单的方法,后来改用了分页方法。
使用虚拟列表技术,有三点需要搞清楚:
① 使用虚拟技术时,需要将CListCtrl控件的Owner Data属性设置为ture。
② 给虚拟列表添加元素时,不需要使用InserItem函数,通过调用SetItemCount设置数据总个数,然后由系统产生不同的消息,在相应的消息响应函数中完成插入工作。
③ 虚拟列表向父窗口发送的消息有三种: ⑴ 当它需要数据时,发送LVN_GETDISPINFO消息; ⑵ 当用户试图查找某个元素时,发送LVN_ODFINDITEM消息; ⑶当需要缓冲数据时,发送 LVN_ODCACHEHINT消息。
当我们使用LVN_GETDISPINFO 的消息处理函数来插入元素时,必须首先检查列表请求的是什么数据(如LVIF_TEXT、LVIF_IMAGE等),然后插入不同的子项。示例如下:
[cpp] view plain copy print ? void CDataAnalysis::OnLvnGetdispinfoAnalysisList(NMHDR *pNMHDR, LRESULT *pResult) { NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR); // TODO: Add your control notification handler code here LV_ITEM* pItem= &(pDispInfo)->item; int iItemIndex= pItem->iItem; size_t converted = 0; wchar_t wStr[30]; //Unicode字符串 if (pItem->mask & LVIF_TEXT) //字符串缓冲区有效 { switch(pItem->iSubItem) { case 0: //填充数据项的名字,xxxxx表示要填充的字符 mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE); lstrcpy(pItem->pszText,wStr); break; case 1: //填充子项1 mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE); lstrcpy(pItem->pszText,wStr); break; case 2: //填充子项2 mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE); lstrcpy(pItem->pszText,wStr); break; case 3: //填充子项3 lstrcpy(pItem->pszText,xxxxxx); break; } } *pResult = 0; } void CDataAnalysis::OnLvnGetdispinfoAnalysisList(NMHDR *pNMHDR, LRESULT *pResult) { NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR); // TODO: Add your control notification handler code here LV_ITEM* pItem= &(pDispInfo)->item; int iItemIndex= pItem->iItem; size_t converted = 0; wchar_t wStr[30]; //Unicode字符串 if (pItem->mask & LVIF_TEXT) //字符串缓冲区有效 { switch(pItem->iSubItem) { case 0: //填充数据项的名字,xxxxx表示要填充的字符 mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE); lstrcpy(pItem->pszText,wStr); break; case 1: //填充子项1 mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE); lstrcpy(pItem->pszText,wStr); break; case 2: //填充子项2 mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE); lstrcpy(pItem->pszText,wStr); break; case 3: //填充子项3 lstrcpy(pItem->pszText,xxxxxx); break; } } *pResult = 0; }
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
针对上述回调函数,有以下几点需要搞清楚:
① 对于参数lparam1和lparam2,分别为CListCtrl的两行数据,是用于比较的对象。通过CListCtrl的成员函数SetItemData来设置,该函数原型:
int SetItemData(int nIndex, DWORD_PTR dwItemData )
其第一个参数为行号,第二个参数指明了该行对应的参数。参数dwItemData 通常设为一行参数的数组,如: pData[2][2] = {{1, 3},{2, 3}}; 每次使用pData[i]作为dwItemData。
② 对于参数lParamSort,用于指明列项,即第几列。该参数和回调函数一同通过CListCtrl的成员函数SortItems来设置,其函数原型为:
BOOL SortItems( PFNLVCOMPARE pfnCompare,DWORD_PTR dwData )
参数 pfnCompare 为回调函数入口地址, 参数dwData 为列项。
③ SetItemData在初始插入数据时进行调用来设置,SortItems则在点击列表头时响应的消息处理函数中进行设置。
示例如下:
[cpp] view plain copy print ? //初始化列表视图控件 BOOL CDataAnalysis::InitListCtl() { //其他处理,包括设置风格,插入列等等 //插入行 for(int i=0; i<LineNum; i++) { //要将char*转换为wchar_t* mbstowcs_s(&converted, wStr, 30, m_analysis[i].Date, _TRUNCATE); m_listAnalysis.InsertItem(i, wStr); //日期 mbstowcs_s(&converted, wStr, 30, m_analysis[i].Time, _TRUNCATE); m_listAnalysis.SetItemText(i, 1, wStr); //时间 mbstowcs_s(&converted, wStr, 30, m_analysis[i].ID, _TRUNCATE); m_listAnalysis.SetItemText(i, 2, wStr); //ID m_listAnalysis.SetItemText(i, 3, m_analysis[i].lpszEvent); //事件 //设置回调函数的参数 m_listAnalysis.SetItemData(i, (LPARAM)(m_analysis+i)); } return TRUE; } void CDataAnalysis::OnHdnItemclickAnalysisList(NMHDR *pNMHDR, LRESULT *pResult) { LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR); // TODO: Add your control notification handler code here //设置回调函数的参数和入口地址 m_listAnalysis.SortItems(SortFunc, phdr->iItem); *pResult = 0; } //排序的回调函数 int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { int result; //返回值 //两行的参数,用于比较 ANALYSISFORMAT* pAnalysis1 = (ANALYSISFORMAT*)lParam1; ANALYSISFORMAT* pAnalysis2 = (ANALYSISFORMAT*)lParam2; //排序 switch(lParamSort) { case 0: //日期 result = strcmp(pAnalysis1->Date, pAnalysis2->Date); break; case 1: //时间 result = strcmp(pAnalysis1->Time, pAnalysis2->Time); break; case 2: //ID result = strcmp(pAnalysis1->ID, pAnalysis2->ID); break; case 3: //事件 result = wcscmp(pAnalysis1->lpszEvent, pAnalysis2->lpszEvent); break; default: break; } return result; } //初始化列表视图控件 BOOL CDataAnalysis::InitListCtl() { //其他处理,包括设置风格,插入列等等 //插入行 for(int i=0; i<LineNum; i++) { //要将char*转换为wchar_t* mbstowcs_s(&converted, wStr, 30, m_analysis[i].Date, _TRUNCATE); m_listAnalysis.InsertItem(i, wStr); //日期 mbstowcs_s(&converted, wStr, 30, m_analysis[i].Time, _TRUNCATE); m_listAnalysis.SetItemText(i, 1, wStr); //时间 mbstowcs_s(&converted, wStr, 30, m_analysis[i].ID, _TRUNCATE); m_listAnalysis.SetItemText(i, 2, wStr); //ID m_listAnalysis.SetItemText(i, 3, m_analysis[i].lpszEvent); //事件 //设置回调函数的参数 m_listAnalysis.SetItemData(i, (LPARAM)(m_analysis+i)); } return TRUE; } void CDataAnalysis::OnHdnItemclickAnalysisList(NMHDR *pNMHDR, LRESULT *pResult) { LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR); // TODO: Add your control notification handler code here //设置回调函数的参数和入口地址 m_listAnalysis.SortItems(SortFunc, phdr->iItem); *pResult = 0; } //排序的回调函数 int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { int result; //返回值 //两行的参数,用于比较 ANALYSISFORMAT* pAnalysis1 = (ANALYSISFORMAT*)lParam1; ANALYSISFORMAT* pAnalysis2 = (ANALYSISFORMAT*)lParam2; //排序 switch(lParamSort) { case 0: //日期 result = strcmp(pAnalysis1->Date, pAnalysis2->Date); break; case 1: //时间 result = strcmp(pAnalysis1->Time, pAnalysis2->Time); break; case 2: //ID result = strcmp(pAnalysis1->ID, pAnalysis2->ID); break; case 3: //事件 result = wcscmp(pAnalysis1->lpszEvent, pAnalysis2->lpszEvent); break; default: break; } return result; }有时需要向上或向下移动表项内容,这里给出向上移动的方法,向下移动的方法类似。
① 利用第2节所述的内容获取行号nItem,判断行号是否为行首,如果不是行首则进入②;
② 获取第nItem行的所有子项内容;
③ 删除第nItem行,并在nItem-1的位置重新插入原先的第nItem行的内容;
④ 使nItem-1的位置高亮显示
示例如下:
[cpp] view plain copy print ? /*************************上移子项**************************/ void CStudyTestDlg::OnPageup() { if (nItem == 0) { MessageBox("该子项已经位于第一行!"); return; } // 提取内容 CString temp[4]; int i; for(i=0;i<4;i++) temp[i] = m_ListCtrl.GetItemText(nItem, i); // 删除 m_ListCtrl.DeleteItem(nItem); // 在nItem-1位置处插入 for (i=0; i<4; i++) m_ListCtrl.SetItemText(nItem-1,i,temp[i]); //高亮显示 UINT flag = LVIS_SELECTED|LVIS_FOCUSED; m_ListCtrl.SetItemState(--nItem, flag, flag); } /*************************下移子项**************************/ void CStudyTestDlg::OnPagedown() { if (nItem == m_ListCtrl.GetItemCount()-1) { MessageBox("该子项已经位于最后一行!"); return; } // 提取内容 CString temp[4]; int i; for (i=0; i<4; i++) temp[i] = m_ListCtrl.GetItemText(nItem, i); // 删除 m_ListCtrl.DeleteItem(nItem); // 在nItem+1位置处插入 for (i=0; i<4; i++) m_ListCtrl.SetItemText(nItem+1, i,temp[i]); //高亮显示 UINT flag = LVIS_SELECTED|LVIS_FOCUSED; m_ListCtrl.SetItemState(++nItem, flag, flag); } /*************************上移子项**************************/ void CStudyTestDlg::OnPageup() { if (nItem == 0) { MessageBox("该子项已经位于第一行!"); return; } // 提取内容 CString temp[4]; int i; for(i=0;i<4;i++) temp[i] = m_ListCtrl.GetItemText(nItem, i); // 删除 m_ListCtrl.DeleteItem(nItem); // 在nItem-1位置处插入 for (i=0; i<4; i++) m_ListCtrl.SetItemText(nItem-1,i,temp[i]); //高亮显示 UINT flag = LVIS_SELECTED|LVIS_FOCUSED; m_ListCtrl.SetItemState(--nItem, flag, flag); } /*************************下移子项**************************/ void CStudyTestDlg::OnPagedown() { if (nItem == m_ListCtrl.GetItemCount()-1) { MessageBox("该子项已经位于最后一行!"); return; } // 提取内容 CString temp[4]; int i; for (i=0; i<4; i++) temp[i] = m_ListCtrl.GetItemText(nItem, i); // 删除 m_ListCtrl.DeleteItem(nItem); // 在nItem+1位置处插入 for (i=0; i<4; i++) m_ListCtrl.SetItemText(nItem+1, i,temp[i]); //高亮显示 UINT flag = LVIS_SELECTED|LVIS_FOCUSED; m_ListCtrl.SetItemState(++nItem, flag, flag); }http://blog.csdn.net/zwgdft/article/details/7394318
有时由于不确定软件运行时的电脑屏幕大小,需要根据屏幕大小动态设置CListCtrl控件的大小。动态大小的设置时,需要注意不要将高度和宽度设置的超过区域限制,否则就没有滚动条了,导致部分内容无法查看。以我遇到的一个例子来说,其情况见第12节提到的那篇博文所述:将View划分为三个窗格,在左上角View上有个CPropertySheet,其上有几个CPropertyPage,每个属性页上有个CListCtrl,供用户查看信息。那么这时需要设置的CListCtrl的大小即为:
宽度 = 左上角View宽度
高度 = 左上角View高度 - 属性页的Tab项高度
调用MoveWindow函数进行设置即可。
------------------全文完--------------------