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

本文轉自: http://blog.csdn.net/zwgdft/article/details/7560592


本文根據本人在項目中的應用,來談談CListCtrl的部分用法及技巧。當初學習時,查了很多資料,零零碎碎的作了些記錄,現在主要是來做個總結,方便以後查閱。主要包括以下十三點內容:基本操作、獲取選中行的行號、複選框操作、動態設置選中行的字體顏色、設置選中行的背景顏色、禁止拖動表頭、讓第一列居中顯示、設置行高與字體、虛擬列表技術、點擊表頭時進行歸類、向上與向下移動、動態調整大小問題、避免閃爍問題。

      分爲兩篇來進行總結。本篇重點總結:基本操作獲取選中行的行號複選框操作動態設置選中行的字體顏色設置選中行的背景顏色


  1、基本操作

     分別從下面四點來介紹CListCtrl的基本操作:

     ①設置列表視圖顯示方式

      Ⅰ. CListCtrl有四種樣式:LVS_ICON、LVS_SMALLICON、LVS_LIST、LSV_REPORT,可通過控件屬性來設置。本文所述均爲LSV_REPORT屬性。

      Ⅱ. 擴展樣式:

      常用的擴展樣式有三種:LVS_EX_FULLROWSELECT、LVS_EX_GRIDLINES、LVS_EX_CHECKBOXES,分別對應作用 選中某行時使正行高亮、設置網格線、item前生成Ckeckbox控件。

      使用SetExtendedStyle(style)函數設置擴展樣式,使用GetExtendedStyle()函數獲取樣式,如:

                       m_listInfo.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
       Ⅲ. 使用CListView時,需要在PreCreateWindow()函數中添加  cs.style | =  LVS_REPORT;

來將其設置爲LVS_REPORT風格,否則插入無效。還用另一種方法來設置風格,即在OnInitialUpate()中獲取CListCtrl控制權,然後修改風格,如下所示:

                      CListCtrl &theCtrl =GetListCtrl();

                      theCtrl.ModifyStyle(0, LVS_REPORT);

     ②插入操作

        先插入列:

                       int InsertColumn( int nCol, LPCTSTRlpszColumnHeading, int nFormat, int nWidth, int nSubItem)

插入列時,可指明列號、列名稱、列名稱顯示樣式,列寬等信息。對於列號爲0的那一列,始終是靠左顯示,後面會有修改使其劇中顯示的方法,其他列通過設置nFormat屬性可以居中顯示。

        插入行:

                       int InsertItem( int nItem, LPCTSTRlpszItem )

直接插入一行,nItem指明行號,lpszItem指明該行第0列的信息。

       設置信息:

                       BOOL SetItemText(int nItem,  int nSubItem, LPCTSTR lpszText )

設置第nItem行nSubItem列的信息(nItem:0,1,2,3……; nSubItem:1,2,3……)

     ③刪除操作

       有三個操作函數:

                       BOOL DeleteAllItems()  -------刪除所有的行

                       BOOL DeleteItem(nItem) --------刪除某一行

                       BOOL DeleteColumn(nCol) -----刪除某一列

     ④獲取/設置屬性函數

      有很多函數了,就不一一介紹了。常用的有

                      int GetItemCount() -------- 獲取已插入信息的行數

                      BOOL SetItemState(int iLink, UINTstate, UINTstateMask ) ---------設置行狀態,如高亮顯示等

等等


  2、獲取選中行的行號

       獲取選中行的行號,然後對該行進行相關處理,這點在編程中用的非常多。

       當鼠標單擊item時,控件向父窗口發送NM_CLICK消息,其響應函數爲OnNMClickXXXX(NMHDR *pNMHDR, LRESULT *pResult),在該函數下來編寫代碼獲取鼠標點擊的行號。

       有兩種方法來獲取行號:第一種是使用GetFirstSelectedItemPositionGetNextSelectedItem配合來獲取;第二種是先獲取鼠標位置信息,然後調用HitTest函數來找出行號。示例分別如下:

        第一種方法,該示例截自MSDN,可作修改後使用。

 
POSITION pos = pList->GetFirstSelectedItemPosition();
if (pos == NULL)
   TRACE0("No items were selected!\n");
else
{
   while (pos)
   {
      int nItem = pList->GetNextSelectedItem(pos);
      TRACE1("Item %d was selected!\n", nItem);
      // you could do your own processing on nItem here
   }
}

        第二種方法,該示例來自我的項目,可作修改後使用。
 
//獲取單擊所在的行號
//找出鼠標位置
DWORD dwPos = GetMessagePos();
CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
m_listCtrl.ScreenToClient(&point);

 //定義結構體
LVHITTESTINFO lvinfo;
lvinfo.pt = point;

 //獲取行號信息
int nItem = m_listCtrl.HitTest(&lvinfo);
if(nItem != -1)
    m_itemSel = lvinfo.iItem;	//當前行號

      對於LVHITTESTINFO 結構體,其有四個成員,在上述HitTest調用中,其第一個成員作爲輸入,另外三個作爲輸出。具體變量含義可查看MSDN。
typedef struct _LVHITTESTINFO {
    POINT pt;
    UINT flags;
    int iItem;
    int iSubItem;
} LVHITTESTINFO, *LPLVHITTESTINFO;

  3複選框操作

       有時需要在item前面添加一個CheckBox,供用戶選擇,然後對所有選中項進行處理。

       這裏涉及到兩個問題:第一個,如何添加CheckBox風格;第二個,如何判斷某一行的CheckBox狀態是否發生改變。

       對於第一個問題,在基本操作裏已經有所闡述了,即通過SetExtendedStyle函數添加LVS_EX_CHECKBOXES擴展風格。

      這裏重點探討第二個問題,首先,操作複選框狀態的有兩個函數:

                      BOOL GetCheck(int nItem)-------獲取複選框狀態

                      BOOL SetCheck( int nItem, BOOL fCheck = TRUE )-------設置複選框狀態

其次,我們要搞清楚以下四點:

當列表的項item改變時,控件會向父窗口發送LVN_ITEMCHANGED消息,因此可以在LVN_ITEMCHANGED消息的響應函數中對複選框的狀態進行處理(查詢或設置)。

鼠標點擊CheckBox時,消息的順序是 NM_CLICK —> LVN_ITEMCHANGED,即CheckBox的狀態是在 NM_CLICK消息函數結束後纔會發生變化,在NM_CLICK中使用GetCheck無效。

鼠標點擊Item(非CheckBox區域)時,消息的順序是 LVN_ITEMCHANGED —> NM_CLICK。

調用InsertItem 函數時,也會產生LVN_ITEMCHANGED消息。鑑於此,通常會自定義一個BOOL型變量m_bHit 來判斷是點擊操作還是插入操作,該變量初始賦FALSE,當有鼠標點擊item時賦TRUE, 檢測完是否有CheckBox被點擊後重新復位爲FALSE。

        示例如下所示:

void CXXXX::OnNMClickXXXX(NMHDR *pNMHDR, LRESULT *pResult)
{
    //獲取單擊所在的行號
    //找出鼠標位置
    DWORD dwPos = GetMessagePos();
    CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
    m_listCtrl.ScreenToClient(&point);
    //定義結構體
    LVHITTESTINFO lvinfo;
    lvinfo.pt = point;
    //獲取行號信息
    int nItem = m_listCtrl.HitTest(&lvinfo);
    if(nItem != -1)
	m_itemSel = lvinfo.iItem;	//當前行號

     //判斷是否點擊在CheckBox上
     if(lvinfo.flags == LVHT_ONITEMSTATEICON)
          m_bHit = TRUE;

     *pResult = 0;
}

void CXXXX::OnLvnItemchangedXXXX(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	//判斷m_bHit,即是否點擊了CheckBox
	if(m_bHit)
	{
	        m_bHit = FALSE;		//復位

		if(m_listCtrl.GetCheck(m_itemSel))
		{       //CheckBox被選中
			//do your own processing 
		}
		else
		{      //CheckBox取消選擇
			//do your own processing 
		}
	}

	*pResult = 0;
}


  4、動態設置選中行的字體顏色

         有時可能需要設置某行的文字爲特殊顏色,以表示某種特殊含義,比如正在下載的信息用綠色,暫停下載的用灰色。

         首先,給出一個CodeProject的鏈接,這篇文章講的非常好,主要是利用Custom Draw。http://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra    

         然後,來談談我的方法,這裏主要談對選中行的字體顏色進行動態修改,當然也是我通過上面文章和自己實踐結合得出的。

        我們需要搞清楚以下幾點(可以結合下面修改某一行的字體顏色的方法來看):

① 當控件繪製時,會發送NM_CUSTOMDRAW 消息,該消息的消息響應函數爲

void CXXXX::OnNMCustomdrawXXXX(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
	// TODO: Add your control notification handler code here
	*pResult = CDRF_DODEFAULT;
       //………………
 }
②其中,pNMHDR爲輸入參數,其指向NMLVCUSTOMDRAW結構體,該結構包含了很多信息,包括字體顏色、背景等等,特別是第一個成員,爲NMCUSTOMDRAW結構體變量,其包含了Current drawing stage(不知道怎麼編譯比較好),其可能的值如下圖(截自MSDN)所示


pResult爲輸出參數,該參數決定了接下來向windows發送什麼消息(與繪製有關的),通過發送該消息我們可以進入下一步需要的處理階段。具體輸出哪個值取決於Current drawing stage,其可能的值如下圖(截自MSDN)所示


④ 有一點必須注意(英文的,我覺得看起來比翻譯過來更精確):

     One thing to keep in mind is you must always check the draw stage before doing anything else, because your handler will receive many messages, and the draw stage determines what action your code takes.

        下面我們來看看如何修改某一行的字體顏色:

①  首先,我們應該明白要修改字體顏色,應該在pre-paint 階段來完成

② 因此,在消息響應函數中,我們首先判斷是否處於pre-paint stage(即pLVCD->nmcd.dwDrawStage == CDDS_PREPAINT),然後通過修改輸出值pResult 的值來通知windows我們需要處理每個item的消息(即設置 *pResult = CDRF_NOTIFYITEMDRAW)。

③ 再次進入消息響應函數時,我們判斷是否處於Item的pre-paint stage(即pLVCD->nmcd.dwDrawStage == CDDS_ITEMPREPAINT),如果是則進行相關處理,即修改字體顏色等等。

④ 處理完了後重新設置 *pResult = CDRF_DODEFAULT,表示我們不再需要其他特殊的消息了,默認執行即可。

         示例如下:

void CXXXX::OnNMCustomdrawXXXX(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
    *pResult = CDRF_DODEFAULT;

    // First thing - check the draw stage. If it's the control's pre-paint stage, 
    // then tell Windows we want messages for every item.
    if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
    {
        *pResult = CDRF_NOTIFYITEMDRAW;
    }
    else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
    {
        // This is the notification message for an item. 
	//處理,將item改變背景顏色
        if( /*符合條件*/ )
	    pLVCD->clrText = RGB(255,0,255);
		
           *pResult = CDRF_DODEFAULT;
    }
}


        上面談的方法主要用於設置靜態字體顏色,當然,如果你的列表的信息在不斷變化(即用SetItemText不斷修改),那麼也就實現了動態改變了,否則需要在合適的地方調用重繪函數:

                         BOOL RedrawItems( int nFirst, int nLast )

表示在nFirst和nLast之間的行需要進行重繪。


  5、設置選中行的背景顏色


         設置選中行的背景顏色,可以將選中行以特殊顏色顯示,容易明白當前處理的是哪一行。儘管有高亮,但是高亮是基於焦點的,如果你選中了某一行,然後焦點轉移了,這是就無法判斷你選的是哪一行了。

        設置選中行的背景顏色的方法和第四節中講的修改字體顏色的方法是相似的,都是利用Custom Draw。這裏涉及到設置當前選中行爲特殊顏色,同時要恢復前一次選中行的顏色,否則就亂了。因此需要記錄前一次選中行、當前選中行的行號,相信通過前面的總結,這點並不難實現。然後在當前選中行和前一次選中行之間進行重繪即可。

       示例如下:

void CXXXX::OnNMClickXXXX(NMHDR *pNMHDR, LRESULT *pResult)
{
	//…………

	//重繪item,更改背景顏色
	int nFirst = min(m_itemSel,m_itemForeSel);
	int nLast = max(m_itemSel,m_itemForeSel);
	m_listCtrl.RedrawItems(nFirst, nLast);	//在前一次選中的item和當前選中的Item之間進行重繪

	*pResult = 0;
}
void CXXXX::OnNMCustomdrawXXXX(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
	*pResult = CDRF_DODEFAULT;

	// First thing - check the draw stage. If it's the control's prepaint
        // stage, then tell Windows we want messages for every item.
	if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
	{
             *pResult = CDRF_NOTIFYITEMDRAW;
	}
        else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
	{
                // This is the notification message for an item. 
		//處理,將item改變背景顏色
		if(m_itemSel == pLVCD->nmcd.dwItemSpec)		
		{	//當前選中的item
			pLVCD->clrTextBk = RGB(255,0,0);
		}
		else if(m_itemForeSel == pLVCD->nmcd.dwItemSpec)
		{	//前一次選中的item,恢復爲白色
			pLVCD->clrTextBk = RGB(255,255,255);
		}

                *pResult = CDRF_DODEFAULT;
	}
}


發佈了286 篇原創文章 · 獲贊 57 · 訪問量 118萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章