一、设置字体色和背景色
要设置这些颜色,需要用到一个重要的消息: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;
}