項目中用到一個界面如下圖所示,在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);
//插入其他內容......
//......
}
}