CListCtrl的高级用法

本文介绍了如何使用MFC自定义列表视图的颜色显示、实现表项的上下移动及响应标题栏点击进行排序等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、设置字体色和背景色

要设置这些颜色,需要用到一个重要的消息:NM_CUSTOMDRAW.

该消息其实是一个WM_NOTIFY消息,也就是CListCtrl向其父窗口发送的通知。

那么Windows什么时候发送这个消息呢?就在CListCtrl或其中的Item绘制的时候,具体来讲,有以下四种时候:

1.当一个Item绘制之前

2.当一个Item绘制之后

3.当一个Item擦除之前

4.当一个Item擦出之后

而我们只需关心其中第一个。

我们先把这个消息事件添加进来吧,右键我们的CListCtrl,选择属性,在属性面板里面点击控件事件,找到NM_CUSTOMDRAW消息,添加该处理函数。

我们对默认的处理函数稍作修改:

void CXXXDlg::OnNMCustomdrawList1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMLVCUSTOMDRAW pNMLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);//这个结构里面含有我们需要的东西
	*pResult = CDRF_DODEFAULT;//这个值等于0
}
pNMLVCD里的成员clrText和clrTextBk可以分别设置字体色和背景色,但我们首先关心的是其第一个成员nmcd的成员dwDrawStage,

即pNMLVCD->nmcd.dwDrawState,这个成员指出了当程序在处理NM_CUSTOMDRAW消息时,究竟是上面四大类的哪种情况触发了该消息。

pResult是一个输出参数,该参数表示我们想要接收哪些通知。

先来以一段代码为例:

 

void CXXXDlg::OnCustomdrawList1 ( NMHDR* pNMHDR, LRESULT* pResult )
{
 LPNMLVCUSTOMDRAW pNMLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
 pResult = CDRF_DODEFAULT;//如果我们后面不改变*pResult的值的话,那么其为默认值,表示让系统默认绘制
 if ( CDDS_PREPAINT == pNMLVCD->nmcd.dwDrawStage )//如果CListCtrl的状态为CDDS_PREPAINT(绘制之前,或者叫预绘制?)
  pResult = CDRF_NOTIFYITEMDRAW;//告诉系统我们想要收到每个item绘制时的通知
 else if ( CDDS_ITEMPREPAINT == pNMLVCD->nmcd.dwDrawStage )//上面的通知起作用了,我们收到了每个item进行绘制的通知了
 {
  if(pNMLVCD->nmcd.dwItemSpec==1||pNMLVCD->nmcd.dwItemSpec==2)//如果是第2行或者是第3行
  {
   pNMLVCD->clrText=RGB(255,255,0);//把字体颜色设为黄色;
   pNMLVCD->clrTextBk=RGB(255,0,0);//背景色设置为红色
  }
 *pResult = CDRF_DODEFAULT;//我们的设置已完成,让系统自己绘制去吧
}

这样,颜色就设置好了

二、上下移动表项

有时候我们想要上下移动某项,该怎么做呢?

我们以上移为例。基本方法就是该项删掉,然后把它重新插入到上面一行。

代码示例:

void CXXXDlg::OnMoveup()
{
	int nItem=m_ListCtrl.GetSelectionMark();//获取选中的行号
	if(nItem==0)//如果是已经是第一行,则什么都不做。
		return;
	TCHAR szStr[4][100];//四个缓冲区,用以获取当前item的各项内容。如果你的ListCtrl有5列,那就定义5个缓冲区。
	for(int nSubItem=0;nSubItem<4;nSubItem++)
		m_ListCtrl.GetItemText(nItem,nSubItem,szStr[nSubItem],100);//循环获得该行每列的内容
	m_ListCtrl.DeleteItem(nItem);//删除该行
	m_ListCtrl.InsertItem(nItem-1,szStr[0]);//插入至前一行
	for(int nSubItem=1;nSubItem<4;nSubItem++)
		m_ListCtrl.SetItemText(nItem-1,nSubItem,szStr[nSubItem]);//设置插入行的其他列
	m_ListCtrl.SetItemState(nItem-1,LVIS_SELECTED|LVIS_FOCUSED,LVIS_SELECTED|LVIS_FOCUSED);//设置高亮和选中状态
}
向下移动则同理。

三、点击表的标题进行排序

首先我们添加一个标题点击的事件。同样在ListCtrl的控件事件中,找到HDN_ITEMCLICK事件,添加该事件。会生成如下代码:

void CXXXDlg::OnHdnItemclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	*pResult = 0;
}
我们在其中添加这样的代码:

m_ListCtrl.SortItems(SortFunc,phdr->iItem);
这里调用了ListCtrl的SortItem函数,该函数原型如下:

BOOL SortItems( PFNLVCOMPARE pfnCompare, DWORD dwData );
其第一个参数pfnCompare是一个回调函数,由我们自己定义。当我们调用SortItem时,系统会自己调用这个回调函数来进行排序。

回调函数的原型如下:

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2,LPARAM lParamSort);
如果你这个函数设为类成员函数,则必须为static的。

该函数前两个参数我们下面再将,第三参数由上面一个函数的dwData传入,通常我们传入的是我们点击的第几列,如上面的

phdr->iItem正是代表了我们点击的是第几列。

那么前两个参数是什么意思呢?lParam其实是一个32位的值,ListCtrl里的每一个Item都可以和一个32位值相关联,系统进行排序的时候,就会比较每两行的lParam值,根据比较结果进行排序。(当然它有它的算法,它应该不会只是傻傻得使用冒泡法吧)

现在的问题是,一个Item怎么和一个32位值关联起来。还记得我们上一讲的SetItemData函数吗?是的,正是利用这个函数,可以将每一个Item和一个数值关联起来。
我们来看一个简单的例子。

首先我们要有一个ListCtrl,然后插入列标题,这个自己插吧,插多少列自己决定。

然后我们这样插入四行:

<span style="white-space:pre">	</span>m_ListCtrl.InsertItem(0,TEXT("第四行"));
	m_ListCtrl.SetItemText(0,1,TEXT("0"));
	m_ListCtrl.SetItemData(0,0);
	m_ListCtrl.InsertItem(1,TEXT("第三行"));
	m_ListCtrl.SetItemText(1,1,TEXT("1"));
	m_ListCtrl.SetItemData(1,1);
	m_ListCtrl.InsertItem(2,TEXT("第二行"));
	m_ListCtrl.SetItemText(2,1,TEXT("2"));
	m_ListCtrl.SetItemData(2,2);
	m_ListCtrl.InsertItem(3,TEXT("第一行"));
	m_ListCtrl.SetItemText(3,1,TEXT("3"));
	m_ListCtrl.SetItemData(3,3);
我们就插入了这样的四行,并且分别为每一行设置了不同的SetItemData。
我们这样写我们自己的排序回调函数:

int CALLBACK CXXXDlg::SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)//注意该函数必须是静态成员函数。设为全局函数也行。
{
	switch(lParamSort)//对列进行switch
	{
	case 0:
		break;
	case 1://如果是第二列
		{
			if(lParam1<lParam2)
				return 1;//返回值大于0,降序排列
			else
				return -1;//返回值小于0,则升序排列
		}
	case 2:
		break;
	case 3:
		break;
	}
	return 0;//返回值等于0,顺序不变。
}
这样如果我们点击标题的第二列,就会按我们制定的方法排序了。点击其他列则不会排序。

当然这只是一个简单的示例。一般情况下,我们的ListCtrl都有好多个列,我们希望点击每一个列,都会按照这个列的内容来进行排序。这是我们设置给每个Item的32位值不能再是这样简单的数值了,而可以定为一个指针,而这个指针指向的对象包含整个列表的所有的情况,根据这些我们就可以分别排序了。

有时候我们希望点击一列的时候升序排列,再点击这一列的时候就降序排序,怎么办呢?
我的方法是这样:给这个列准备一个变量,初始值为1.每当点击这个列的时候,这个值就乘上-1.而在排序函数里面,返回的值要乘上这个变量,这样就可以实现。

一个稍完善的例子:

首先准备一个结构,存放列表行数据。

struct CListData
{
	CString m_strXingming;
	CString m_strXuehao;
	CString m_strChengji;
};
CListData listdata[4]={{TEXT("张三"),TEXT("018"),TEXT("95")},{TEXT("李四"),TEXT("015"),TEXT("96")},
{TEXT("王五"),TEXT("030"),TEXT("90")},{TEXT("赵六"),TEXT("020"),TEXT("88")}};//准备存四行数据
插入列表头:

m_ListCtrl.InsertColumn(0,TEXT("姓名"),0,150);
m_ListCtrl.InsertColumn(1,TEXT("学号"),0,150);
m_ListCtrl.InsertColumn(2,TEXT("成绩"),0,150);	
插入四行数据,同时设置每行的关联数据为该行listdata的指针:
for(int iIndex=0;iIndex<4;iIndex++)
	{
		m_ListCtrl.InsertItem(iIndex,listdata[iIndex].m_strXingming);
		m_ListCtrl.SetItemText(iIndex,1,listdata[iIndex].m_strXuehao);
		m_ListCtrl.SetItemText(iIndex,2,listdata[iIndex].m_strChengji);
		m_ListCtrl.SetItemData(iIndex,DWORD(listdata+iIndex));
	}
在对话框类里面添加一个静态数组成员,用以控制升序和降序:

static int m_nSort[3];
初始化这个数组:

int CXXXDlg::m_nSort[3]={1,1,1};
点击列表头事件:

void CXXXDlg::OnHdnItemclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
	m_ListCtrl.SortItems(SortFunc,phdr->iItem);//对该列排序
	m_nSort[phdr->iItem]*=-1;//点击的哪一列,哪一列的数字就乘-1.
	*pResult = 0;	
}
排序函数:

int CALLBACK CXXXDlg::SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	CListData* pListData1=(CListData*)lParam1,*pListData2=(CListData*)lParam2;
	int ret=0;
	switch(lParamSort)
	{
	case 0://第一列
		{
			ret= pListData1->m_strXingming<pListData2->m_strXingming?-1:1;
			ret*=m_nSort[0];
		}
		break;
	case 1://第二列
		{
			ret = pListData1->m_strXuehao<pListData2->m_strXuehao?-1:1;
			ret*=m_nSort[1];
		}
		break;
	case 2://第三列
		{
			ret=pListData1->m_strChengji<pListData2->m_strChengji?-1:1;
			ret*=m_nSort[2];
		}
		break;
	default:
		break;
	}
	return ret;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值