现在的位置: 首页 > 综合 > 正文

支持文件拖放和实时监控的列表控件CListCtrlEx

2013年11月26日 ⁄ 综合 ⁄ 共 16587字 ⁄ 字号 评论关闭

CListCtrlEx:一个支持文件拖放和实时监视的列表控件——用未公开API函数实现Shell实时监视

一、需求
无论何时,当你在Explorer窗口中创建、删除或重命名一个文件夹/文件,或者插入拔除移动存储器时,Windows总是能非常快速地更新它所有的视图。有时候我们的程序中也需要这样的功能,以便当用户在Shell中作出创建、删除、重命名或其他动作时,我们的应用程序也能快速地随之更新。
二、原理
Windows内部有两个未公开的函数(注:在最新的MSDN中,已经公开了这两个函数),分别叫做SHChangeNotifyRegister和SHChangeNotifyDeregister,可以实现以上的功能。这两个函数位于Shell32.dll中,是用序号方式导出的。这就是为什么我们用VC自带的Depends工具察看Shell32.dll时,找不到这两个函数的原因。SHChangeNotifyRegister的导出序号是2;而SHChangeNotifyDeregister的导出序号是4。
SHChangeNotifyRegister可以把指定的窗口添加到系统的消息监视链中,这样窗口就能接收到来自文件系统或者Shell的通知了。而对应的另一个函数,SHChangeNotifyDeregister,则用来取消监视钩挂。SHChangeNotifyRegister的原型和相关参数如下:

SHChangeNotifyRegister的原型和相关参数如下:
ULONG SHChangeNotifyRegister
(          
HWND  hwnd,
    int   fSources,
    LONG  fEvents,
    UINT    wMsg,
    Int  cEntries,
    SHChangeNotifyEntry *pfsne
);

其中:
hwnd
将要接收改变或通知消息的窗口的句柄。
fSource
指示接收消息的事件类型,将是下列值的一个或多个(注:这些标志没有被包括在任何头文件中,使用者须在自己的程序中加以定义或者直接使用其对应的数值)
SHCNRF_InterruptLevel
0x0001。接收来自文件系统的中断级别通知消息。
SHCNRF_ShellLevel
0x0002。接收来自Shell的Shell级别通知消息。 
SHCNRF_RecursiveInterrupt
0x1000。接收目录下所有子目录的中断事件。此标志必须和SHCNRF_InterruptLevel 标志合在一起使用。当使用该标志时,必须同时设置对应的SHChangeNotifyEntry结构体中的fRecursive成员为TRUE(此结构体由函数的最后一个参数pfsne指向),这样通知消息在目录树上是递归的。
SHCNRF_NewDelivery
0x8000。接收到的消息使用共享内存。必须先调用SHChangeNotification_Lock,然后才能存取实际的数据,完成后调用SHChangeNotification_Unlock函数释放内存。
fEvents
要捕捉的事件,其所有可能的值请参见MSDN中关于SHChangeNotify函数的注解。
wMsg
产生对应的事件后,发往窗口的消息。
cEntries
pfsne指向的数组的成员的个数。
pfsne
SHChangeNotifyEntry结构体数组的起始指针。此结构体承载通知消息,其成员个数必须设置成1,否则SHChangeNotifyRegister或者SHChangeNotifyDeregister将不能正常工作(但是据我试验,如果cEntries设为大于1的值,依然可以注册成功,不知何故)。
如果函数调用成功,则返回一个整型注册标志号,否则将返回0。同时系统就会将hwnd指定的窗口加入到操作监视链中,当有文件操作发生时,系统会向hwnd标识的窗口发送wMsg指定的消息,我们只要在程序中加入对该消息的处理函数就可以实现对系统操作的监视了。
如果要退出程序监视,就要调用另外一个未公开得函数SHChangeNotifyDeregister来取消程序监视。该函数的原型如下:
BOOL SHChangeNotifyDeregister(ULONG ulID);
其中ulID指定了要注销的监视注册标志号,如果卸载成功,返回TRUE,否则返回FALSE。
三、实例
在前一文中,我们派生了一个支持文件拖放的列表控件CListCtrlEx,但它还有一个小小的缺憾,就是当用户把一个文件拖放进来之后,如果用户在Shell中对该文件进行移动、删除、重命名等操作时,CListCtrlEx将不能保证实时更新。经过上面的讨论,现在就让我们为CListCtrlEx加上实时文件监控功能。
在使用这两个函数之前,必须要先声明它们的原型,同时还要添加一些宏和结构定义。我们在原工程中添加一个ShellDef.h头文件,然后加入如下声明:
#define SHCNRF_InterruptLevel  0x0001 //Interrupt level notifications from the file system
#define SHCNRF_ShellLevel   0x0002 //Shell-level notifications from the shell
#define SHCNRF_RecursiveInterrupt  0x1000 //Interrupt events on the whole subtree
#define SHCNRF_NewDelivery   0x8000 //Messages received use shared memory

\r\n

typedef struct 
{
    LPCITEMIDLIST pidl; //Pointer to an item identifier list (PIDL) for which to receive notifications
    BOOL fRecursive; //Flag indicating whether to post notifications for children of this PIDL
}SHChangeNotifyEntry;

\r\n

typedef struct
{
    DWORD dwItem1;  // dwItem1 contains the previous PIDL or name of the folder. 
    DWORD dwItem2;  // dwItem2 contains the new PIDL or name of the folder. 
}SHNotifyInfo;

\r\n

typedef ULONG
(WINAPI* pfnSHChangeNotifyRegister)
(
 HWND hWnd,
 int  fSource,
 LONG fEvents,
 UINT wMsg,
 int  cEntries,
 SHChangeNotifyEntry* pfsne 
);

\r\n

typedef BOOL (WINAPI* pfnSHChangeNotifyDeregister)(ULONG ulID);
这些宏和函数的声明,以及参数含义,如前所述。下面我们要在CListCtrlEx体内添加两个函数指针和一个ULONG型的成员变量,以保存函数地址和返回的注册号。
接下来我们为CListCtrlEx类添加一个公有成员函数Initialize,在其中,我们首先进行加载Shell32.dll以及初始化函数指针动作,接着调用注册函数向Shell注册。用户在使用我们提供的CListCtrlEx类时,应当在CListCtrlEx创建完毕后跟着调用Initialize函数以确保注册了消息监视钩子。否则将失去实时监视功能。初始化函数如下(已略去涉及OLE初始化的部分):
BOOL CListCtrlEx::Initialize()
{
 …………
 //加载Shell32.dll
 m_hShell32 = LoadLibrary(\"Shell32.dll\");
 if(m_hShell32 == NULL) 
 {
  return FALSE;
 }

\r\n

 //取函数地址
 m_pfnDeregister = NULL;
 m_pfnRegister = NULL;
 m_pfnRegister = (pfnSHChangeNotifyRegister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(2));
 m_pfnDeregister = (pfnSHChangeNotifyDeregister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(4));
 if(m_pfnRegister==NULL || m_pfnDeregister==NULL)
 {
  return FALSE;
 }

\r\n

 SHChangeNotifyEntry shEntry = {0};
 shEntry.fRecursive = TRUE;
 shEntry.pidl = 0;
 m_ulNotifyId = 0;
 
 //注册Shell监视函数
 m_ulNotifyId = m_pfnRegister(
        GetSafeHwnd(),
        SHCNRF_InterruptLevel|SHCNRF_ShellLevel,
        SHCNE_ALLEVENTS,
        WM_USERDEF_FILECHANGED, //自定义消息
        1,
        &shEntry
       );
 if(m_ulNotifyId == 0)
 {
  MessageBox(\"Register failed!\",\"ERROR\",MB_OK|MB_ICONERROR);
  return FALSE;
 }
 return TRUE;
}
在CListCtrlEx类中再添加如下函数。该函数的作用是从PIDL中解出实际字符路径。
CString CListCtrlEx::GetPathFromPIDL(DWORD pidl)
{
    char szPath[MAX_PATH];
    CString strTemp = _T(\"\");
    if(SHGetPathFromIDList((struct _ITEMIDLIST *)pidl, szPath))
 {
        strTemp = szPath;
    }
    return strTemp;
}
    现在我们的程序就可以接收到来自Shell或文件系统的通知消息了,只要编写一个处理自定义消息的函数,就可以对系统范围内的更改动作作出我们希望的响应动作。这里lParam参数中存放的是当前发生的事件(譬如SHCNE_CREATE),wParam参数中存放的是SHNotifyInfo结构。下面是一段框架代码:
LRESULT CListCtrlEx::OnFileChanged(WPARAM wParam, LPARAM lParam)
{
    CString  strOriginal = _T(\"\");
 CString  strCurrent = _T(\"\");
 SHNotifyInfo* pShellInfo = (SHNotifyInfo*)wParam;

\r\n

 strOriginal = GetPathFromPIDL(pShellInfo->dwItem1);
 if(strOriginal.IsEmpty())
 {
  return NULL;
 }

\r\n


问题描述
项目中有这样的需求,要求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;

1. CListCtrl 样式及设置

2. 扩展样式设置

3. 数据插入

4. 一直选中Item

5. 选中和取消选中Item

6. 得到CListCtrl中所有行的checkbox的状态

7. 得到CListCtrl中所有选中行的序号

8. 得到item的信息

9. 得到CListCtrl的所有列的header字符串内容

10. 使CListCtrl中一项可见,即滚动滚动条

11. 得到CListCtrl列数

12. 删除所有列

13. 得到单击的CListCtrl的行列号

14. 判断是否点击在CListCtrl的checkbox上

15. 右键点击CListCtrl的item弹出菜单

16. CListCtrl进行大数据量更新时,避免闪烁

!时间仓促,此播客内容大多为网上整理,特向原作者表示感谢!


1. CListCtrl 样式及设置
 LVS_ICON: 每个item显示大图标
 LVS_SMALLICON: 每个item显示小图标
 LVS_LIST: 显示一列带有小图标的item
 LVS_REPORT: 显示item详细资料

 如windows资源管理器,“查看”标签下的“大图标,小图标,列表,详细资料”

 

 LONG lStyle;
 lStyle=GetWindowLong(m_ListCtrl.m_hWnd, GWL_STYLE); //获取当前窗口style
 lStyle &= ~LVS_TYPEMASK; //清除显示方式位
 lStyle |= LVS_REPORT; //设置style
 SetWindowLong(m_ListCtrl.m_hWnd, GWL_STYLE, lStyle); //设置style


2. 扩展样式设置

 DWORD dwStyle = m_ListCtrl.GetExtendedStyle(); //获取当前扩展样式
 dwStyle |= LVS_EX_FULLROWSELECT; //选中某行使整行高亮(report风格时)
 dwStyle |= LVS_EX_GRIDLINES; //网格线(report风格时)
 dwStyle |= LVS_EX_CHECKBOXES; //item前生成checkbox控件
 m_ListCtrl.SetExtendedStyle(dwStyle); //设置扩展风格


3. 数据插入

 m_ListCtrl.InsertColumn(0,"名称",LVCFMT_LEFT,50); //插入列
 m_ListCtrl.InsertColumn(1,"备注",LVCFMT_LEFT,50);
 

 //直接插入:

 int nRow=m_ListCtrl.InsertItem(0, "VC++");        //插入行
 m_ListCtrl.SetItemText(nRow,1,"Visual C++ 6.0");  //设置数据

 

 //LVITEM 结构插入:

 LVITEM item={0};
 item.iItem=0;  //行号
 item.mask=LVIF_TEXT; /*LVIF_IMAGE支持图标*/;
 item.cchTextMax=15;//插入字符串长度
 item.pszText="Visual C++ 6.0";

 int nRow=m_ListCtrl.InsertItem(&item);

 m_ListCtrl.SetItemText(nRow,1,"Visual C++ 6.0");


4. 一直选中Item

 选中style中的 Show selection always,

 或者添加扩展样式 LVS_SHOWSELALWAYS


5. 选中和取消选中Item
 int nIndex = 0;
 //选中
  m_ListCtrl.SetItemState(nIndex,LVIS_SELECTED|

  LVIS_FOCUSED,LVIS_SELECTED|LVIS_FOCUSED);
 //取消选中
  m_ListCtrl.SetItemState(nIndex,0,LVIS_SELECTED|LVIS_FOCUSED);


6. 得到CListCtrl中所有行的checkbox的状态
 CString str;
 for(int i=0; i<m_ListCtrl.GetItemCount(); i++)

 {
   if(m_ListCtrl.GetItemState(i, LVIS_SELECTED)==

    LVIS_SELECTED || m_ListCtrl.GetCheck(i))
   {
     str.Format(_T("第%d行的checkbox为选中状态"), i);
     AfxMessageBox(str);
    }

  }


7. 得到CListCtrl中所有选中行的序号

 //方法一:
  CString str;
  for(int i=0; i<m_ListCtrl.GetItemCount(); i++)
  {
    if(m_ListCtrl.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
    {
      str.Format(_T("选中了第%d行"), i);
      AfxMessageBox(str);
     }
   }

 //方法二:
  POSITION pos=m_ListCtrl.GetFirstSelectedItemPosition();
  if(pos==NULL)
   TRACE0("No items were selected!/n");
  else
  {
   while(pos)
    {
      int nItem=m_ListCtrl.GetNextSelectedItem(pos);
      TRACE1("Item %d was selected!/n", nItem);
      //添加其他操作

     }
   }


8. 得到item的信息
 TCHAR szBuf[1024];
 LVITEM lvi;
 lvi.iItem = nItemIndex;
 lvi.iSubItem = 0;
 lvi.mask = LVIF_TEXT;
 lvi.pszText = szBuf;
 lvi.cchTextMax = 1024;
 m_ListCtrl.GetItem(&lvi);

 

 关于得到设置item的状态,还可以参考msdn文章
 Q173242: Use Masks to Set/Get Item States in CListCtrl
 http://support.microsoft.com/kb/173242/en-us


9. 得到CListCtrl的所有列的header字符串内容
 LVCOLUMN lvcol;
 char  str[256];
 int   nColNum;
 CString strColumnName[3];//假如有3列

 nColNum = 0;
 lvcol.mask = LVCF_TEXT;
 lvcol.pszText = str;
 lvcol.cchTextMax = 256;
 while(m_list.GetColumn(nColNum, &lvcol))
 { 
   strColumnName[nColNum] = lvcol.pszText;
   nColNum++;
  }


10. 使CListCtrl中一项可见,即滚动滚动条
 m_ListCtrl.EnsureVisible(i, FALSE);


11. 得到CListCtrl列数
 int nHeadNum=m_ListCtrl.GetHeaderCtrl()->GetItemCount();


12. 删除所有列
 方法一:
  while(m_ListCtrl.DeleteColumn(0))
  //因为你删除了第一列后,后面的列会依次向上移动。

 方法二:
  int nColumns = 3; //列数
  for(int i=nColumns-1; i>=0; i--)

   m_ListCtrl.DeleteColumn(i);


13. 得到单击的CListCtrl的行列号
 添加listctrl控件的NM_CLICK消息相应函数
 void CMyDlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
 {
  //方法一:

  DWORD dwPos=GetMessagePos();
  CPoint point( LOWORD(dwPos), HIWORD(dwPos));

  m_ListCtrl.ScreenToClient(&point);
  

  LVHITTESTINFO lvinfo;
  lvinfo.pt=point;
  lvinfo.flags=LVHT_ABOVE;
  

  int nItem=m_ListCtrl.SubItemHitTest(&lvinfo);
  if(nItem!=-1)
  {
    CString strtemp;
    strtemp.Format("单击的是第%d行第%d列",

    lvinfo.iItem, lvinfo.iSubItem);
    AfxMessageBox(strtemp);
   }

  // 方法二:
  NM_LISTVIEW* pNMListView=(NM_LISTVIEW*)pNMHDR;
  if(pNMListView->iItem!=-1)
  {

    CString strtemp;
    strtemp.Format("单击的是第%d行第%d列",
    pNMListView->iItem, pNMListView->iSubItem);
    AfxMessageBox(strtemp);
   }

   *pResult = 0;

 }


14. 判断是否点击在CListCtrl的checkbox上
 添加listctrl控件的NM_CLICK消息相应函数
 void CMyDlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
 {

   DWORD dwPos=GetMessagePos();
   CPoint point(LOWORD(dwPos), HIWORD(dwPos));

   m_ListCtrl.ScreenToClient(&point);
  

   LVHITTESTINFO lvinfo;
   lvinfo.pt=point;
   lvinfo.flags=LVHT_ABOVE;
     
   UINT nFlag;
   int nItem=m_ListCtrl.HitTest(point, &nFlag);
   //判断是否点在checkbox上
   if(nFlag==LVHT_ONITEMSTATEICON)
   {
     AfxMessageBox("点在listctrl的checkbox上");
   } 
   *pResult = 0;
 }


15. 右键点击CListCtrl的item弹出菜单
 添加CListCtrl控件的NM_RCLICK消息相应函数
 void CMyDlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
 {
   NM_LISTVIEW* pNMListView=(NM_LISTVIEW*)pNMHDR;
   if(pNMListView->iItem!=-1)
   {
     DWORD dwPos = GetMessagePos();
     CPoint point( LOWORD(dwPos), HIWORD(dwPos) );

    

     CMenu menu;
     VERIFY( menu.LoadMenu( IDR_MENU1 ) );
     CMenu* popup = menu.GetSubMenu(0);
     ASSERT( popup != NULL );
     popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,

     point.x, point.y, this );

    } 
    *pResult = 0;

 }


16. CListCtrl进行大数据量更新时,避免闪烁
 m_ListCtrl.SetRedraw(FALSE);
 //更新内容
 m_ListCtrl.SetRedraw(TRUE);
 m_ListCtrl.Invalidate();
 m_ListCtrl.UpdateWindow();
 //或者参考


转载地址:http://blog.csdn.net/hityct1/archive/2008/03/26/2219811.aspx

CListCtrl::SortItems的用法:

(一)SortItems使用在哪?

CListCtrl::SortItems的原型是:
BOOL SortItems( PFNLVCOMPARE pfnCompare, DWORD dwData );
其中
1)第一个参数pfnCompare为回调函数,形式为:
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, 
   LPARAM lParamSort);

lParam1,lParam2是什么?这是SortItems难理解的原因。在(二)中介绍。
lParamSort实际上是列数,等于2)中的dwData。
2)第二个参数dwData为用户自定义值。
dwData实际传入的是列数,等于1)中的lParamSort。

下面是SortItems使用的地方:

//LVN_COLUMNCLICK消息响应函数
void CUpListCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
         NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

        //排序
        
//CompareFunc是回调函数
        
//pNMListView->iSubItem就是列数
         SortItems( (PFNLVCOMPARE)CompareFunc, pNMListView->iSubItem );

        *pResult = 0;
}

(二)SortItems的的回调函数的中的lParam1,lParam2是什么?

简单的说:是LV_ITEM::lParam。
LV_ITEM是个结构,见msdn。

可见,我们在向CListCtrl插入item时,必须使用
int InsertItem( const LVITEM* pItem );
InsertItem有好几种形式,只有这种形式才能够使用LV_ITEM::lParam。

下面是插入item的代码的一个示例:

     tagInfo *pFileInfo = new tagInfo;//tagInfo是个结构,存储了你排序的所需要的信息。
     pFileInfo->strFileName = strFileName;
     pFileInfo->strFileSize = FormatFileSize(filefind.GetLength());
     pFileInfo->strFileType = GetTypeName(lpszFileName);
    //pFileInfo->strFilePath = strPath;

    int nItem = GetItemCount();
    int nIcon = GetIconIndex(lpszFileName, filefind.IsDirectory(), FALSE);   
    
      LV_ITEM lvi;
     lvi.mask = LVIF_TEXT|LVIF_PARAM|LVIF_IMAGE;
     lvi.iItem = nItem;
     lvi.iSubItem = 0;
     lvi.pszText = (LPTSTR)(LPCTSTR)pFileInfo->strFileName;
     lvi.lParam = (LPARAM)pFileInfo;
     lvi.iImage = nIcon;
    
        if( (nItem = InsertItem(&lvi)) != -1 )//插入文件名(即第0列),并显示相应图标
    {    
        //MessageInt(nItem);
         lvi.mask = LVIF_TEXT;        
         lvi.iItem = nItem;

        //设置第1列(即设置文件大小)        
         lvi.iSubItem = 1;            
        if(!filefind.IsDirectory())//如果不是目录
        {    
             lvi.pszText = (LPTSTR)(LPCTSTR)pFileInfo->strFileSize;
             SetItem( &lvi );
         }
        else//如果是目录
        {        
             lvi.pszText = (LPTSTR)YCT_UNKNOW_SIZE;
             SetItem( &lvi );
         }                    

抱歉!评论已关闭.