关于CListView和CListCtrl的排序!

    今天用到了这些知识,所以记忆下来,方便以后查询!

  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的附加数据里面添加该行的索引,这其实并不绝对,你也可以添加别的数据,只要有益于排序就行了。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。