今天用到了这些知识,所以记忆下来,方便以后查询!
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的附加数据里面添加该行的索引,这其实并不绝对,你也可以添加别的数据,只要有益于排序就行了。