MFC總結之CListCtrl用法及技巧(二)
本篇重點介紹:禁止拖動表頭、讓第一列居中顯示、設定行高與字型、虛擬列表技術、點選表頭時進行歸類、向上與向下移動、動態調整大小問題、避免閃爍問題。
6、禁止拖動表頭
過載OnNotify訊息響應函式,遮蔽兩個訊息通知碼:HDN_BEGINTRACKW 和HDN_DIVIDERDBLCLICKW。示例如下:BOOL CXXXX::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { // TODO: Add your specialized code here and/or call the base class //遮蔽兩個訊息通知碼,使得禁止拖動List表頭 NMHEADER* pNMHeader = (NMHEADER*)lParam; if(((pNMHeader->hdr.code == HDN_BEGINTRACKW) | (pNMHeader->hdr.code == HDN_DIVIDERDBLCLICKW))) { *pResult = TRUE; return TRUE; } return CDialog::OnNotify(wParam, lParam, pResult); }
7、讓第一列居中顯示
在插入列時,我們可以通過引數nFormat來設定文字居中顯示,但是這種設定對於第一列是沒有作用的。這時我們可以考慮將我們的內容從第二列開始插入(設定為居中顯示)。先插入第一列,然後刪除第一列,這樣原先的第二列就充當了第一列。8、設定行高和字型
設定CListCtrl的行高沒有函式介面,可以通過自繪來實現,但是比較麻煩。有一個比較簡單的方法是通過使用一個空白的影象將行撐起來,使其高度發生變化。示例如下:對於字型的設定,我們可以使用SetFont函式來實現。以修改CListView的字型為例,在OnInitialUpdate函式中插入列之前呼叫SetFontSelf函式(該函式自定義,如下示例所示)。首先建立一個字型,然後呼叫SetFont進行設定。需要注意的是,在退出時需要delete 掉建立的字型,避免記憶體洩露。CImageList m_image; m_image.Create(1,24,ILC_COLOR32,1,0); m_listInfo.SetImageList(&m_image, LVSIL_SMALL);
//設定字型和大小 void CMyListView::SetFontSelf(int nHeight, LPCTSTR lpszFacename) { //先刪除原有字型 if(m_font != NULL) delete m_font; m_font = new CFont; //建立字型 m_font->CreateFont( nHeight, // nHeight 0, // nWidth 0, // nEscapement 0, // nOrientation FW_NORMAL, // nWeight FALSE, // bItalic FALSE, // bUnderline 0, // cStrikeOut ANSI_CHARSET, // nCharSet OUT_DEFAULT_PRECIS, // nOutPrecision CLIP_DEFAULT_PRECIS, // nClipPrecision DEFAULT_QUALITY, // nQuality DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily lpszFacename); // lpszFacename //設定字型 CListCtrl &theCtrl = GetListCtrl(); //獲取控制權,引用變數 theCtrl.SetFont(m_font, TRUE); }
9、虛擬列表技術
給一個連結,介紹的比較詳細:http://hi.baidu.com/qi_xian/blog/item/929b04ce27d02c0592457ef8.html當資料量大時,使用InsertItem插入資料的過程是很漫長的。這時我們有兩個方法來解決該問題:一是使用CListCtrl的虛擬列表技術,二是採用分頁顯示的方法。對於虛擬列表技術,上述連結中的文章講的很詳細,我用過它的比較簡單的方法,後來改用了分頁方法。
使用虛擬列表技術,有三點需要搞清楚:
① 使用虛擬技術時,需要將CListCtrl控制元件的Owner Data屬性設定為ture。
② 給虛擬列表新增元素時,不需要使用InserItem函式,通過呼叫SetItemCount設定資料總個數,然後由系統產生不同的訊息,在相應的訊息響應函式中完成插入工作。
③ 虛擬列表向父視窗傳送的訊息有三種: ⑴ 當它需要資料時,傳送LVN_GETDISPINFO訊息; ⑵ 當用戶試圖查詢某個元素時,傳送LVN_ODFINDITEM訊息; ⑶當需要緩衝資料時,傳送 LVN_ODCACHEHINT訊息。
當我們使用LVN_GETDISPINFO 的訊息處理函式來插入元素時,必須首先檢查列表請求的是什麼資料(如LVIF_TEXT、LVIF_IMAGE等),然後插入不同的子項。示例如下:
void CDataAnalysis::OnLvnGetdispinfoAnalysisList(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
// TODO: Add your control notification handler code here
LV_ITEM* pItem= &(pDispInfo)->item;
int iItemIndex= pItem->iItem;
size_t converted = 0;
wchar_t wStr[30]; //Unicode字串
if (pItem->mask & LVIF_TEXT) //字串緩衝區有效
{
switch(pItem->iSubItem)
{
case 0: //填充資料項的名字,xxxxx表示要填充的字元
mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE);
lstrcpy(pItem->pszText,wStr);
break;
case 1: //填充子項1
mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE);
lstrcpy(pItem->pszText,wStr);
break;
case 2: //填充子項2
mbstowcs_s(&converted, wStr, 30, xxxxxx, _TRUNCATE);
lstrcpy(pItem->pszText,wStr);
break;
case 3: //填充子項3
lstrcpy(pItem->pszText,xxxxxx);
break;
}
}
*pResult = 0;
}
10、點選表頭時進行歸類排序
系統通過傳送LVM_SORTITEMS訊息來處理歸類問題,在該訊息的處理函式中需要呼叫一個回撥函式,這個回撥函式需要我們來設計,以完成不同的歸類方法。回撥函式原型如下:int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
針對上述回撥函式,有以下幾點需要搞清楚:
① 對於引數lparam1和lparam2,分別為CListCtrl的兩行資料,是用於比較的物件。通過CListCtrl的成員函式SetItemData來設定,該函式原型:
int SetItemData(int nIndex, DWORD_PTR dwItemData )
其第一個引數為行號,第二個引數指明瞭該行對應的引數。引數dwItemData 通常設為一行引數的陣列,如: pData[2][2] = {{1, 3},{2, 3}}; 每次使用pData[i]作為dwItemData。
② 對於引數lParamSort,用於指明列項,即第幾列。該引數和回撥函式一同通過CListCtrl的成員函式SortItems來設定,其函式原型為:
BOOL SortItems( PFNLVCOMPARE pfnCompare,DWORD_PTR dwData )
引數 pfnCompare 為回撥函式入口地址, 引數dwData 為列項。
③ SetItemData在初始插入資料時進行呼叫來設定,SortItems則在點選列表頭時響應的訊息處理函式中進行設定。
示例如下:
//初始化列表檢視控制元件
BOOL CDataAnalysis::InitListCtl()
{
//其他處理,包括設定風格,插入列等等
//插入行
for(int i=0; i<LineNum; i++)
{
//要將char*轉換為wchar_t*
mbstowcs_s(&converted, wStr, 30, m_analysis[i].Date, _TRUNCATE);
m_listAnalysis.InsertItem(i, wStr); //日期
mbstowcs_s(&converted, wStr, 30, m_analysis[i].Time, _TRUNCATE);
m_listAnalysis.SetItemText(i, 1, wStr); //時間
mbstowcs_s(&converted, wStr, 30, m_analysis[i].ID, _TRUNCATE);
m_listAnalysis.SetItemText(i, 2, wStr); //ID
m_listAnalysis.SetItemText(i, 3, m_analysis[i].lpszEvent); //事件
//設定回撥函式的引數
m_listAnalysis.SetItemData(i, (LPARAM)(m_analysis+i));
}
return TRUE;
}
void CDataAnalysis::OnHdnItemclickAnalysisList(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
// TODO: Add your control notification handler code here
//設定回撥函式的引數和入口地址
m_listAnalysis.SortItems(SortFunc, phdr->iItem);
*pResult = 0;
}
//排序的回撥函式
int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
int result; //返回值
//兩行的引數,用於比較
ANALYSISFORMAT* pAnalysis1 = (ANALYSISFORMAT*)lParam1;
ANALYSISFORMAT* pAnalysis2 = (ANALYSISFORMAT*)lParam2;
//排序
switch(lParamSort)
{
case 0: //日期
result = strcmp(pAnalysis1->Date, pAnalysis2->Date);
break;
case 1: //時間
result = strcmp(pAnalysis1->Time, pAnalysis2->Time);
break;
case 2: //ID
result = strcmp(pAnalysis1->ID, pAnalysis2->ID);
break;
case 3: //事件
result = wcscmp(pAnalysis1->lpszEvent, pAnalysis2->lpszEvent);
break;
default:
break;
}
return result;
}
11、向上與向下移動
有時需要向上或向下移動表項內容,這裡給出向上移動的方法,向下移動的方法類似。
① 利用第2節所述的內容獲取行號nItem,判斷行號是否為行首,如果不是行首則進入②;
② 獲取第nItem行的所有子項內容;
③ 刪除第nItem行,並在nItem-1的位置重新插入原先的第nItem行的內容;
④ 使nItem-1的位置高亮顯示
示例如下:
/*************************上移子項**************************/
void CStudyTestDlg::OnPageup()
{
if (nItem == 0)
{
MessageBox("該子項已經位於第一行!");
return;
}
// 提取內容
CString temp[4];
int i;
for(i=0;i<4;i++)
temp[i] = m_ListCtrl.GetItemText(nItem, i);
// 刪除
m_ListCtrl.DeleteItem(nItem);
// 在nItem-1位置處插入
for (i=0; i<4; i++)
m_ListCtrl.SetItemText(nItem-1,i,temp[i]);
//高亮顯示
UINT flag = LVIS_SELECTED|LVIS_FOCUSED;
m_ListCtrl.SetItemState(--nItem, flag, flag);
}
/*************************下移子項**************************/
void CStudyTestDlg::OnPagedown()
{
if (nItem == m_ListCtrl.GetItemCount()-1)
{
MessageBox("該子項已經位於最後一行!");
return;
}
// 提取內容
CString temp[4];
int i;
for (i=0; i<4; i++)
temp[i] = m_ListCtrl.GetItemText(nItem, i);
// 刪除
m_ListCtrl.DeleteItem(nItem);
// 在nItem+1位置處插入
for (i=0; i<4; i++)
m_ListCtrl.SetItemText(nItem+1, i,temp[i]);
//高亮顯示
UINT flag = LVIS_SELECTED|LVIS_FOCUSED;
m_ListCtrl.SetItemState(++nItem, flag, flag);
}
12、避免閃爍問題
這個問題在我的前面一篇博文有提到。13、動態調整大小
有時由於不確定軟體執行時的電腦螢幕大小,需要根據螢幕大小動態設定CListCtrl控制元件的大小。動態大小的設定時,需要注意不要將高度和寬度設定的超過區域限制,否則就沒有滾動條了,導致部分內容無法檢視。以我遇到的一個例子來說,其情況見第12節提到的那篇博文所述:將View劃分為三個窗格,在左上角View上有個CPropertySheet,其上有幾個CPropertyPage,每個屬性頁上有個CListCtrl,供使用者檢視資訊。那麼這時需要設定的CListCtrl的大小即為:
寬度 = 左上角View寬度
高度 = 左上角View高度 - 屬性頁的Tab項高度
呼叫MoveWindow函式進行設定即可。
------------------全文完--------------------