今天用到了这些知识,所以记忆下来,方便以后查询!
CListView的排序和CListCtrl的排序基本相似,所以在这里一并提一下。
什么时候排序?
当用户点击表头的时候,自然要触发排序函数,进行排序。
如上图所示,点击时间这一列头,要触发排序。
如何响应点击表头这一动作?
点击表头时,触发LVN_COLUMNCLICK消息,我们只需要添加相应的函数就行了。在CListCtrl里时触发LVN_COLUMNCLICK,在CListView里面确是触发==LVN_COLUMNCLICK,也就是反射消息。
一般来说,排序的时候我们的列头都要显示一张向上或者向下的图标,以提醒用户是升序排还是降序排,所以在响应LVN_COLUMNCLICK消息的时候我们一般要建立一张图像列表。
你要在你的对话框程序里面加一个CImageList对象(CListCtrl),或者在你的CXXListView(CListView派生类)类里加一个CImageList对象,指针也行,析构的时候记得删除就行了。
在对话框程序的InitDialog函数,或者在CXXListView的InitialUpdate函数里面添加要用到的图片。
void CXXListView::OnInitialUpdate() { CListView::OnInitialUpdate(); CListCtrl &list = GetListCtrl(); //得到内置的ClistCtrl引用 pHeaderImg = new CImageList; //pHeaderImg是CImageList对象的指针,成员变量 pHeaderImg->Create(22, 22, ILC_COLOR32 | ILC_MASK, 2, 2); //创建一个图像列表 CBitmap b1, b2, b3; b1.LoadBitmap(IDB_DOWN); //向下图片 b2.LoadBitmap(IDB_UP); //向上图片 b3.LoadBitmap(IDB_BLANK); //空白图片 pHeaderImg->Add(&b1, RGB(255, 255, 255)); pHeaderImg->Add(&b2, RGB(255, 255, 255)); pHeaderImg->Add(&b3, RGB(255, 255, 255)); list.GetHeaderCtrl()->SetImageList(pHeaderImg); //设置列头的图像列表 }
看一下我添加的三张bitmap:
至于为什么要添加一张空白的位图,我后面自然会说明。
还有在Dlg中或者CXXListView里面还要添加一个int型的变量,用来记录之前点击的列数,如下面的代码中m_nCurSortCol就是这样一个变量,初始化m_nCurSortCol = -1,还需要添加一个变量用来记录排序的方向,BOOL型就可以满足需求了,因为就两种状态,向上排或者向下排,下面代码里的m_bOrder就是这么一个变量,初始化m_bOrder
= FALSE.
图片添加好了之后我们就可以来排序了。在ClistCtrl里面你大可以这么写:
void CXXDlg::OnColumnclick(NMHDR *pNMHDR, LRESULT *pResult) { NMLISTVIEW *p = (NM_LISTVIEW *)pNMHDR; int nSub = p->iSubItem; /*得到点击的列数*/ CHeaderCtrl *pHeader = m_list.GetHeaderCtrl(); /*得到列头的指针,m_list是你的对话框程序里面的CListCtrl控件对应的对象*/ HDITEM hdi = { HDI_IMAGE | HDI_FORMAT }; /*关于HDITEM,你可以视他为一些控制信息的集合*/ /*pHeader可以通过GetItem(m_nCurSortCol, &hdi);获得m_nCurSortCol列的一些信息,如是不是显示图像之类的*/ /*pHeader可以也通过SetItem(m_nCurSortCol, &hdi)设置m_nCurSortCol列的一些状态,如显示图像之类的*/ if (nSub != m_nCurSortCol) /*点击了不同的列*/ { if (m_nCurSortCol > -1) { pHeader->GetItem(m_nCurSortCol, &hdi); /*获取当前有哪些开关状态*/ hdi.iImage &= ~HDF_IMAGE; /*移除图标*/ pHeader->SetItem(m_nCurSortCol, &hdi); } m_nCurSortCol = nSub; } else /*点击了相同的列*/ { m_bOrder = !m_bOrder; } pHeader->GetItem(nSub, &hdi); /*获取开关状态*/ hdi.fmt |= HDF_IMAGE; /*显示图标*/ hdi.iImage = m_bOrder; /*图标方向,实际上是图像列表里面的图标的索引号*/ pHeader->SetItem(nSub, &hdi); /*设置表头表项的状态*/ //很有必要,将索引项填入每一个item的附加数据里面 int num = m_list.GetItemCount(); /*获得总共的列数*/ while(num--) m_list.SetItemData(num, num); PFNLVCOMPARE fns[] = { bySender, bySubject, byTime }; /*三个排序函数*/ m_list.SortItems(fns[m_nCurSortCol], (DWORD)this); //开始排序,很有必要将自己的指针传给排序函数 *pResult = 0; }
一般来说,这么写没一点错误,对于CXXListView来说,却有一些问题,我不知道是只有我自己遇到了这样的问题还是怎么的,那就是图片屏蔽不了了,对于一列,我可以将它的图片开关打开,但是却关不了,点击了一列之后,再点击另外一列,原来一列的图标并不消失,真是奇了怪了,不过也有方法解决,这也是我为什么载入一张空白位图的原因,既然关不了,那就画一张空白位图吧,效果相同。
void CXXListView::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR); int nSub = pNMLV->iSubItem; /*得到点击的列数*/ CHeaderCtrl *pHeader = GetListCtrl().GetHeaderCtrl(); HDITEM hdi = { HDI_IMAGE | HDI_FORMAT }; if (nSub != m_nCurSortCol) /*点击了不同的列*/ { if (m_nCurSortCol > -1) { pHeader->GetItem(m_nCurSortCol, &hdi); /*获取当前有哪些开关状态*/ hdi.iImage = 2; /*改变图标索引,2指向原来的空白位图*/ pHeader->SetItem(m_nCurSortCol, &hdi); } m_nCurSortCol = nSub; } else /*点击了相同的列*/ { m_bOrder = !m_bOrder; /*m_bOrder是BOOL类型,用来几录当前的排序顺序,这里反序*/ } pHeader->GetItem(nSub, &hdi); /*获取开关状态*/ hdi.fmt |= HDF_IMAGE; /*显示图标*/ hdi.iImage = m_bOrder; /*设置图标索引,BOOL其实就是0, 1*/ pHeader->SetItem(nSub, &hdi); int num = GetListCtrl().GetItemCount(); //获得行数 while(num--) //设置该列的索引号到附加数据里面 GetListCtrl.SetItemData(num, num); PFNLVCOMPARE fns[] = { bySender, bySubject, byTime }; GetListCtrl().SortItems(fns[m_nCurSortCol], (DWORD)this); *pResult = 0; }
前面的bySender, bySubject, byTime其实都是排序函数,这些函数需要你自己来实现,我实现一个就行了,其余都类似,需要注意的是,这些函数都必须是静态函数,基本上是这种形式:
static int CALLBACK bySender(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);//按照发件人排序
然后怎么排序呢?
lparam1和lparam2并不是要比较行的索引,而是要比较行的附加数据,也就是你之前SetItemData里面的数据,我们这样来排序,假设按照发件人来排序,先获取数据,再比较。还是以CXXListView为例:
int CALLBACK CXXView::bySender(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) /*按照发件人排序*/ { CXXListView *pView = (CEmailListView *)lParamSort; int n1 = lParam1; //获得附加数据,前面存入item的行数 int n2 = lParam2; CString str1 = pView->GetListCtrl().GetItemText(n1, 0); //获取发件人那一行的文字 CString str2 = pView->GetListCtrl().GetItemText(n2, 0); if (pView->m_bOrder) //根据排序顺序来排 return str2 > str1; else return str1 > str2; }
bySubject, byTime也是类似的写,怎么排序,看你自己的排法了,看一下我的成果吧!
PS:往一个Item的附加数据里面添加该行的索引,这其实并不绝对,你也可以添加别的数据,只要有益于排序就行了。