1. 程式人生 > >MFC總結之CListCtrl用法及技巧(二)

MFC總結之CListCtrl用法及技巧(二)

        本篇重點介紹:止拖動表頭、讓第一列居中顯示、設定行高與字型、虛擬列表技術、點選表頭時進行歸類、向上與向下移動、動態調整大小問題、避免閃爍問題

 6、禁止拖動表頭

       過載OnNotify訊息響應函式,遮蔽兩個訊息通知碼:HDN_BEGINTRACKWHDN_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的行高沒有函式介面,可以通過自繪來實現,但是比較麻煩。有一個比較簡單的方法是通過使用一個空白的影象將行撐起來,使其高度發生變化。示例如下:
CImageList m_image;
m_image.Create(1,24,ILC_COLOR32,1,0);
m_listInfo.SetImageList(&m_image, LVSIL_SMALL);
        對於字型的設定,我們可以使用SetFont函式來實現。以修改CListView的字型為例,在OnInitialUpdate函式中插入列之前呼叫SetFontSelf函式(該函式自定義,如下示例所示)。首先建立一個字型,然後呼叫SetFont進行設定。需要注意的是,在退出時需要delete 掉建立的字型,避免記憶體洩露。
//設定字型和大小
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函式進行設定即可。

------------------全文完--------------------