解決CListCtrl閃爍及水平滾動條不能跟蹤拖動的問題(MFC)

       項目中用到一個界面如下圖所示,在View上有個CPropertySheet,其上有幾個CPropertyPage,每個屬性頁上有個CListCtrl,供用戶查看信息。由於CListCtrl中的信息每隔200ms就要刷新一次,導致這個區域不停的閃爍。同時,由於空間不夠,CListCtrl上有個水平滾動條,當我們想拉動滾動條到中間位置查看後面幾列數據時,剛拉過去就由於刷新又被拉回起點,導致很難查看後面幾列數據,我們將這個問題稱爲水平滾動條不能跟蹤問題。本文主要就這兩點來記錄相關的解決方法。

(1)解決閃爍問題

      我們知道,造成繪圖閃爍的原因是因爲在每次重繪之前會調用OnEraseBkgnd用背景色對區域進行擦除(默認爲白色),導致前後圖像反差太大,引起視覺上的閃爍。要去除閃爍,就是要降低這種反差。一種較好的方法就是採用雙緩衝繪圖,即在內存中開闢一個畫布,用來繪圖,然後將繪製好的圖形複製到設備中顯示(http://www.diybl.com/course/3_program/c++/cppjs/200867/123361.html)。另外,防止CListCtrl閃爍的問題,網友也有總結(http://blog.sina.com.cn/s/blog_5ee42ba30100g50j.html)。

     在查閱上述資料,結合本身的問題,設計如下的解決方案,來解決閃爍問題。

    首先,由於顯示數據全部在CListCtrl上更新,對於View、CPropertySheet、CPropertyPage而言,並沒有什麼改動,每次重繪時可以禁止其用背景色擦除區域,減少反差。分別重載這三者的OnEraseBkgnd消息函數,改成 return FALSE;

    其次,對於CListCtrl閃爍的問題,可以採用雙緩衝來解決。如下所示:

void CMyListCtrl::OnPaint()
{
	//使用雙緩衝的方法繪製背景
	CPaintDC dc(this); // device context for painting
	CRect rect;
	CRect headerRect;
	CDC MenDC;		//內存DC   
	CBitmap MemMap;

	GetClientRect(&rect);    
	GetDlgItem(0)->GetWindowRect(&headerRect);   
	MenDC.CreateCompatibleDC(&dc);   
	MemMap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height()); 
	MenDC.SelectObject(&MemMap);
	MenDC.FillSolidRect(&rect,RGB(255,255,255));   

	//調用默認的OnPaint(),把圖形畫在內存DC表上   
	DefWindowProc(WM_PAINT,(WPARAM)MenDC.m_hDC,(LPARAM)0);   

	//輸出到顯示設備
	dc.BitBlt(0,
		headerRect.Height(),   
		rect.Width(),   
		rect.Height(),   
		&MenDC,   
		0,     
		headerRect.Height(),   
		SRCCOPY);   
	MenDC.DeleteDC();
	MemMap.DeleteObject();
}

BOOL CMyListCtrl::OnEraseBkgnd(CDC* pDC)
{
	// TODO: Add your message handler code here and/or call default

	//return CListCtrl::OnEraseBkgnd(pDC);
	return FALSE;
}
     至此,可解決整體的閃爍問題。

(2)解決水平滾動條不能跟蹤問題

      對於這個問題,我一開始比較迷茫,不知道怎麼解決,從網上搜了一下,有人提出用GetScrollPos + Scroll,也有人提出用EnsureVisible。第一個方法我試了,主要是記錄當前滾動條的位置,然後在刷新後設置到記錄的位置,這個方法並不能很好的回到記錄的位置,且這樣做的話會重新引入閃爍問題(滾動條從記錄位置到起始位置再到記錄位置,反差很大)。第二種方法貌似只能用於多行,對於多列好像不行。

     於是,我重新思考這個問題。我在程序裏的做法是每次刷新時,首先刪除所有的行項,然後重新插入包含新數據信息的行項。其實這個行數還是保持不變的,每次更新的只是一些Item的內容而已,於是我想能不能每次只是更新這些數據,不進行行項的刪除和重新插入操作,除非有新的行項加入才進行這個操作。如果還無法回到滾動條位置,那麼能否根據當前view的視圖大小,計算當前需要更新的CListCtrl的Item項,即每次不對所有的Item列項進行更新數據,只對當前View顯示視圖區域內的內容進行更新(後來發現這點已經不必做了,第一點已經能夠解決這個問題)。

      對程序做了更改後,可以實現水平滾動條的跟蹤問題。

void UpdateItem()
{
   //......
     CString str;
     BOOL bInsertItem = FALSE;
     if(m_bInsertItem)
     {
        m_bInsertItem = FALSE;
	bInsertItem = TRUE;
	m_listInfo.DeleteAllItems();		//刪除原有的數據
     }
     for(i=0; i<CNT; i++)
     {  
        str.Format(_T("%d"), i+1);			//序號
	if(bInsertItem)
	    m_listInfo.InsertItem(i, str);		//插入行,顯示序號
	str.Format(_T("ID=%d"), xxx);
	m_listInfo.SetItemText(i, 1, str);
       //插入其他內容......
       //......
     }
} 



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