CListCtrl按列排序

 
CListCtrl 的排序问题
目录:
问题描述:... 1
解决方案:... 2
Failed solution:2
Successful Solution. 3
1. CompareFunc两个比较参数的设置... 3
2.CompareFunc 回调函数的实现... 4
3. 调用SortItems5
4.Header头三角的实现... 5
5. 对于不同数据类型的比较... 6
References... 6
问题描述
项目中有这样的需求,要求list控件支持各个列的排序。
MFC CListCtrl本身提供了一个函数来支持排序:
CListCtrl::SortItems
原型:
BOOL SortItems(
   PFNLVCOMPARE pfnCompare,
   DWORD_PTR dwData
);
Parameters
pfnCompare
回调函数。每次调用 SortItems 来排序时 Window 会自动调用这个回调函数。
所以我们可以把对各种数据类型的比较处理逻辑封装在这个函数中。例如: CompareDate CompareNumber CompareString,etc.
值得注意的是:
该函数要么定义为类的静态成员函数, 要么定义为独立于全局函数。
dwData
Application-defined value that is passed to the comparison function.
一般情况下,可以把这个 pointer 定义为指向CListCtrl对象的指针。
 
Remarks
The comparison function has the following form:
int CALLBACK CompareFunc( LPARAM lParam1, LPARAM lParam2,
   LPARAM lParamSort);
The comparison function must return a negative value if the first item should precede the second, a positive value if the first item should follow the second, or zero if the two items are equivalent.
The lParam1 parameter is the 32-bit value associated with the first item being compared, and the lParam2 parameter is the value associated with the second item. These are the values that were specified in the lParam member of the items' LVITEM structure when they were inserted into the list. The lParamSort parameter is the same as the dwData value.
注意到 lParam member of the items' LVITEM structure 这句话没,当初也试着实现 list 排序,但技术问题出在:
只能为每行的第一列设置 / 赋值 lParam, 而不能为其他列( SubItem 设置 lParam. 那么 SubItem 的比较参数应该存放在哪呢? 怎么才能实现任意列排序呢?
 
 解决方案:
  Failed solution:
LVITEM lv_item;
lv_item.mask = LVIF_IMAGE|LVIF_TEXT| LVIF_PARAM;
//restore the text string into lparam for compare
lv_item.lParam = (LPARAM)lpszText;
Int iIndex = CListCtrl::InsertItem(&lv_item); //第一列
//BOOL bRnt = CListCtrl::SetItem(&lv_item); //1,2,3,…列
iIndex 返回非-1 值说明设置成功。
bRnt 返回FALSE。 说明SubItem设置失败。
 
 Successful Solution
 1. CompareFunc两个比较参数的设置
看来要想解决任意列排序问题,除了实现上面提到的回调函数外,还要想办法设置回调函数的参数两个比较参数 lParam1 , lParam2
 
既然只支持设置每一行第一列的 lParam, 那么是不是考虑将整个一行的所有列的text一次性都存放进第一列的 lParam中呢?
 
想到用数组存放整个一行的所有列的text, 然后将该数组地址赋给 lParam, 这样就解决了我们的比较参数设置问题。
具体如下:
//-----------override the InsertItem method to add below codes block--------
int nIndex = CListCtrl::InsertItem(nItem, lpszText);
LPTSTR* lpszArr = new LPTSTR[nNumColumn]; //array for storing all subitem text
//as the first column text
lpszArr[0] = new TCHAR[ lstrlen( lpszText ) + 1 ];
 //set the first subItem text to the first elemant of array
(void)lstrcpy( lpszArr[0], lpszText );
//set the first subItem text to the first elemant of array
CListCtrl::SetItemData(nItem, (DWORD)lpszArr);
 
//-----------override SetItem method to add below codes block-----
BOOL bRnt = CListCtrl::SetItem(nItem, nSubItem, LVIF_TEXT, lpszText, 0, 0, 0, 0);
//Get the pointer which stored all cells text
LPTSTR* lpszArr =
reinterpret_cast < LPTSTR *>( CListCtrl::GetItemData( nItem ) );
//set the sepcify nSubItem item s text into array
lpszArr[nSubItem] = new TCHAR[ lstrlen( lpszText ) + 1 ];
(void)lstrcpy( lpszArr[nSubItem], lpszText );
 
Note: SetItemData/GetItemData 是对LVITEM structure 中的lParam 的访问接口。
 
注意到没,如果完全按照上面的方法做的话,用来存放所有列text的数组占用了整个LVITEM structure中的lParam, 这样的一个扩展类屏蔽了基类提供的SetItemData/GetItemData接口。
 
还得想办法使得扩展类CListCtrlEx仍然有变量存放LVITEM structure中的不定参数lParam, 从而支持SetItemData/GetItemData。
a. 构建一个struct把lpszTestArr 和dwData 封装在一起。
struct LISTITEMINFO
{
       LISTITEMINFO()
       {
              lpszTextArr = NULL;       
dwData = NULL;
             
       }
       LPTSTR* lpszTextArr; //store all subitem s text of a row
       DWORD dwData; //store the lParam
};
 
 
b. 修改上面的codes.
LISTITEMINFO* pid = new LISTITEMINFO;
pid-> lpszTextArr = lpszArr;
//instead of CListCtrl::SetItemData(nItem, (DWORD)lpszArr);
CListCtrl::SetItemData( nItem, (DWORD)pid );
 
 
c. override SetItemData & GetItemData & SetItemText
DWORD CListCtrlEx::GetItemData(int nItem) const
{
       ASSERT( nItem < GetItemCount() );
 
    LISTITEMINFO* pid = reinterpret_cast<LISTITEMINFO*>( CListCtrl::GetItemData( nItem ) );
       ASSERT( pid );
       return pid->dwData;
}
 
 
 
 2.CompareFunc 回调函数的实现
int CALLBACK CListCtrlEx::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
       CListCtrlEx* pListCtrl = reinterpret_cast<CListCtrlEx*>( lParamSort);
       ASSERT( pListCtrl->IsKindOf( RUNTIME_CLASS( CListCtrl ) ) );
 
       LISTITEMINFO* pid1 = reinterpret_cast<LISTITEMINFO*>( lParam1 );
       LISTITEMINFO* pid2 = reinterpret_cast<LISTITEMINFO*>( lParam2 );
 
       ASSERT( pid1 );
       ASSERT( pid2 );
 
       LPCTSTR pszText1 = pid1->lpszTextArr[ pListCtrl->m_iCurSortCol ];
       LPCTSTR pszText2 = pid2->lpszTextArr[ pListCtrl->m_iCurSortCol ];
   
   //add your compare implement according different data type such as //DateTime/Number
 3. 调用SortItems
在Header column 的Click事件处理函数中调用SortItems, 就可实现排序拉。
 
 4.Header头三角的实现
MFC提供了SortItems来支持CListCtrl的排序,但没有提供设置排序标记(头三角)的功能。
 
如果有这样的需求就得自己实现。
方法一、在CHeaderCtrl的扩展类CHeaderCtrlEx中自己绘制三角(up/down state)
 void CHeaderCtrlEx :: DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
 //draw arrow up/down here
}
通过这种方式绘制的Header没有了xp style, 还需要你自己绘制xp风格。
 
方法二:插入图片(两幅图片的替换)
HDITEM hdrItem;
GetHeaderCtrl()-> GetItem(m_iSortCol, & hdrItem);
hdrItem. mask = HDI_FORMAT | HDI_IMAGE;
hdrItem. iImage = nImageIndex; //index of up/down bitmap
 
pHeaderCtrl-> SetItem(m_iSortCol, & hdrItem);
可以说这个方法工作代价最小。而且也没破坏xp风格。
不过不要忘了先 GetHeaderCtrl ()->SetImageList(&m_imageListSort)
 
 5. 对于不同数据类型的比较
可以为每个Column 绑定一个数据类型属性,在CompareFunc中
Switch(columnType)
{
 case DateTimeCol
     …
break;
 case NumberCol:
break;
 
 
 
References
http://support.microsoft.com/kb/250614/en-us
 
http://www.codeguru.com/Cpp/controls/listview/sorting/article.php/c1055#more
 
http://blog.csdn.net/smartlife/archive/2004/11/01/162517.aspx
 

你可能感兴趣的:(CListCtrl按列排序)