當(dāng)我們需要在用戶區(qū)顯示一些圖形時(shí),,先把圖形在客戶區(qū)畫上,雖然已經(jīng)畫好但此時(shí)我們還無法看到,,還要通過程序主動(dòng)地刷新用戶區(qū),,強(qiáng)制Windows發(fā)送一條WM_PAINT消息,這將引發(fā)視類OnDraw函數(shù)簡(jiǎn)單地將所有的圖形對(duì)象重畫,,這樣才完成了圖形的顯示工作,,但在刷新的同時(shí)會(huì)引起較明顯的閃爍尤其是當(dāng)畫面面積較大、圖像元素過多時(shí)尤為明顯甚至達(dá)到無法正常工作的地步,。因此,,我們需要做相應(yīng)的處理。本文介紹了采用先在內(nèi)存中繪制圖形,,然后再把繪好的圖形以位圖方式從內(nèi)存拷貝到窗口客戶的消除刷屏閃爍的一種方法,。
一、 WM_PAINT消息和無效區(qū)
當(dāng)以下情況之一發(fā)生時(shí),,就要求應(yīng)用程序一定刷新其用戶區(qū)的一部分或全部: 1. 在用戶移動(dòng)窗口或顯示窗口時(shí),,窗口中先前被隱藏的區(qū)域重新可見。 2. 用戶改變窗口的大小,。 3. 滾動(dòng)窗口用戶區(qū),。 4. 程序調(diào)用InvalidateRect或InvalidateRgn函數(shù)顯式地發(fā)送一條WM_PAINT消息。 此時(shí)Windows會(huì)向窗口函數(shù)發(fā)送一條WM_PAINT消息,。另外,,當(dāng)Windows刪除覆蓋窗口部分區(qū)域的對(duì)話框或消息框時(shí)和菜單下拉出來又被釋放時(shí)窗口用戶區(qū)被臨時(shí)覆蓋,系統(tǒng)會(huì)試圖保存顯示區(qū)域,,但是不一定能成功,,可能向窗口函數(shù)發(fā)送一條WM_PAINT消息,要求應(yīng)用程序刷新其用戶區(qū),。需要說明的是:光標(biāo)或圖符穿過窗口用戶區(qū)時(shí),,也可能覆蓋顯示內(nèi)容,但這種情況下,,系統(tǒng)一定能保留并恢復(fù)被覆蓋的區(qū)域,,所以此時(shí)并不會(huì)發(fā)送WM_PAINT消息來要求應(yīng)用程序去刷新其顯示區(qū)。在Windows 應(yīng)用程序的窗口函數(shù)中,對(duì)WM_PAINT消息的處理就是刷新其用戶區(qū),,這是一種固定的程序結(jié)構(gòu),。為提高刷新效率,我們可以只刷新用戶區(qū)的一小部分,,其余沒有發(fā)生變化的我們可以不予刷新,,窗口函數(shù)可以通過調(diào)用函數(shù)InvalidateRect顯式地使用戶區(qū)內(nèi)的一個(gè)矩形無效。而且只有當(dāng)窗口客戶區(qū)的某一部分失效時(shí),,其窗口函數(shù)才會(huì)收到WM_PAINT消息,。 二、 刷屏閃爍的產(chǎn)生原因與解決方法
當(dāng)客戶區(qū)有所改動(dòng),,而又要將改動(dòng)顯示出來,,就必然要強(qiáng)制Windows發(fā)送一條WM_PAINT消息,從而引發(fā)OnDraw函數(shù)的重畫,,這樣雖完成了圖形的顯示,,卻也會(huì)引起較明顯的閃爍,當(dāng)畫面上數(shù)據(jù)不是很多時(shí)尚不明顯,,當(dāng)客戶區(qū)有成千上萬個(gè)點(diǎn)的時(shí)候刷新一次會(huì)引起整幅畫面的劇烈跳動(dòng),,尤其是對(duì)于許多實(shí)時(shí)監(jiān)控軟件和矢量電子地圖軟件,此類軟件通常在屏幕上都會(huì)動(dòng)輒幾千,、幾萬個(gè)要素點(diǎn),,很明顯單靠發(fā)送WM_PAINT 消息引發(fā)OnDraw 的重畫根本滿足不了實(shí)際需求。 為了解決上述問題,,我們需要做一些相應(yīng)的處理,。首先要先檢取無效區(qū),然后創(chuàng)建一個(gè)與原設(shè)備環(huán)境句柄pDC相兼容的內(nèi)存設(shè)備環(huán)境,,之后就可以采用在內(nèi)存中繪制圖形并把繪好的圖形以位圖方式從內(nèi)存拷貝到窗口客戶的方法來消除刷屏?xí)r引起的閃爍,。這還需要?jiǎng)?chuàng)建一個(gè)與原設(shè)備環(huán)境句柄pDC相兼容的、大小為整個(gè)客戶區(qū)的位圖,。然后再使新的設(shè)備環(huán)境dc與pDC具有同樣的映射關(guān)系,將位圖選入內(nèi)存環(huán)境,。再使dc的整個(gè)客戶區(qū)都成無效區(qū),,再"與上"所檢取的無效區(qū),使內(nèi)存環(huán)境與pDC檢取的無效區(qū)相等,。之后便可以進(jìn)行繪圖工作了,,繪圖完畢之后應(yīng)當(dāng)釋放所獲取的設(shè)備環(huán)境句柄pDC。否則會(huì)造成系統(tǒng)資源的浪費(fèi),。
三,、 程序示例
本示例程序通過打開任意存檔文件,將其ASCII碼碼值當(dāng)作要顯示的數(shù)據(jù),并通過一圖畫控件將其數(shù)據(jù)以圖形的形式依次顯示出來,。本程序要處理的數(shù)據(jù)量較大,,如不采用本文所述方法將會(huì)有很明顯的閃爍。 (1) 新建一基于CFormView的單文檔應(yīng)用程序WaveShower,。
(2) 在Form上添加一"picture"控件,設(shè)置其ID為IDC_SCREEN,、Type為Rectangle、Color為Black,。在"Extended Styles"屬性頁里選中Modal Frame檢查框,。
(3) 添加一菜單"打開數(shù)據(jù)文件",并生成其響應(yīng)函數(shù)OnOpenData(),。
(4) 在視類中添加如下成員變量:
int m_BufLen; //數(shù)據(jù)長(zhǎng)度 unsigned char* buffer; //數(shù)據(jù)緩存 int m_dx; //數(shù)據(jù)偏移量 int m_DY; //數(shù)據(jù)顯示區(qū)的幅度 CPoint* value; //將要顯示的數(shù)值 int m_DX; //數(shù)據(jù)顯示區(qū)的寬度 int m_Y0; //數(shù)據(jù)顯示區(qū)參照點(diǎn)位置 CRect rect; //數(shù)據(jù)顯示區(qū)矩形 (5) 在視類中添加函數(shù)GetScreenRect()用以獲取數(shù)據(jù)顯示區(qū)的大小及其他參數(shù),;添加函數(shù)CleanScreen()完成清除數(shù)據(jù)顯示區(qū)的功能;添加函數(shù)DrawPoint()以便在數(shù)據(jù)顯示區(qū)畫點(diǎn):
void CWaveShowerView::GetScreenRect()
{ CWnd* pStatic = GetDlgItem(IDC_SCREEN); pStatic->GetWindowRect(&rect); ScreenToClient(&rect); rect.top+=4; rect.left+=4; rect.bottom-=4; rect.right-=4; m_Y0=(rect.bottom-rect.top)/2+rect.top; m_DX=rect.Width(); m_DY=re ct.Height()/2; value=new CPoint[m_DX]; } void CWaveShowerView::CleanScreen()
{ CDC* pDC=GetDC(); CPen pen1(PS_SOLID,1,RGB(0,0,0)); CPen* oldPen1=pDC->SelectObject(&pen1); for(int i=rect.top;i td> { pDC->MoveTo(rect.left,i); pDC->LineTo(rect.right,i); } pDC->SelectObject(&oldPen1); CPen pen2(PS_SOLID,1,RGB(0,0,255)); CPen* oldPen2=pDC->SelectObject(&pen2); pDC->MoveTo(rect.left,m_Y0); pDC->LineTo(rect.right,m_Y0); pDC->SelectObject(&oldPen2); ReleaseDC(pDC); } void CWaveShowerView::DrawPoint(CPoint pt, COLORREF color)
{ CDC* pDC=GetDC(); pDC->SetPixel(rect.left+pt.x,m_Y0-pt.y,color); ReleaseDC(pDC); } (6) 在視類的OnInitialUpdate()初始化函數(shù)中添加代碼以進(jìn)行數(shù)據(jù)顯示的各項(xiàng)前期準(zhǔn)備工作,,并在"打開數(shù)據(jù)文件"菜單的響應(yīng)函數(shù)中添加代碼以讀取文件的內(nèi)碼,。
void CWaveShowerView::OnInitialUpdate()
{ CFormView::OnInitialUpdate(); GetParentFrame()->RecalcLayout(); ResizeParentToFit(); GetScreenRect(); for(int i=0;i td> value[i].x=value[i].y=0; SetTimer(0,10,NULL); } void CWaveShowerView::OnOpenData()
{ CString FileName=""; CFile file; CFileDialog dlg(TRUE,"*","*.*", OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, "所有文件(*.*)|*.*||",NULL); if(dlg.DoModal()==IDOK) { KillTimer(1); FileName=dlg.GetPathName(); file.Open(FileName,CFile::modeReadWrite); m_BufLen=file.GetLength(); buffer= new unsigned char[m_BufLen+m_DX+10]; file.Read(buffer,m_BufLen); file.Close(); SetTimer(1,10,NULL); } } (7)下面添加的定時(shí)器響應(yīng)函數(shù)正是本文的重點(diǎn),為方便對(duì)比起見,,筆者寫了兩個(gè)OnTimer響應(yīng)函數(shù),,前一個(gè)是采用常規(guī)的普通方法描點(diǎn)的,運(yùn)行起來可以很明顯地看到畫面的閃爍跳動(dòng),。而后一種則是采用本文所述方法采用的內(nèi)存畫圖的方法,,運(yùn)行后幾乎畫面無閃爍。下面便是兩段對(duì)比代碼的原碼部分:
//代碼一:有閃爍的代碼
void CWaveShowerView::OnTimer(UINT nIDEvent) { if(nIDEvent==0) { CleanScreen(); for(int i=0;i td> DrawPoint(value[i],RGB(0,255,0)); } if(nIDEvent==1) { m_dx+=2; for(int i=0;i td> { value[i].x=i; if(m_dx+i<0) buffer[m_dx+i]=128; if(m_dx+i<-m_DX) m_dx-=2; if(m_dx+i>m_BufLen) buffer[m_dx+i]=128; if(m_dx+i>m_BufLen+m_DX) m_dx-=2; value[i].y=m_DY* (buffer[m_dx+i]-128)/256; } } CFormView::OnTimer(nIDEvent); } //代碼二:無閃爍的代碼
void CWaveShowerView::OnTimer(UINT nIDEvent) { if(nIDEvent==0) { CDC* pDC=GetDC(); CDC dc; CBitmap bitmap; CBitmap* pOldBitmap; CRect client; pDC->GetCliPBox(client); //檢取無效區(qū) //創(chuàng)建一個(gè)與pDC兼容的內(nèi)存設(shè)備環(huán)境 if(dc.CreateCompatibleDC(pDC)) { //創(chuàng)建一與pDC兼容的位圖,,大小為整個(gè)客戶區(qū) if(bitmap.CreateCompatibleBitmap(pDC,rect.Width(), rect.Height())) { //使dc與pDC具有同樣的映射關(guān)系 OnPrepareDC(&dc,NULL); //將位圖選入內(nèi)存環(huán)境 pOldBitmap=dc.SelectObject(&bitmap); //使dc的整個(gè)客戶區(qū)都成無效區(qū) dc.SelectClipRgn(NULL); //再"與上"檢取的無效區(qū),,使內(nèi)存環(huán)境與 //pDC檢取的無效區(qū)相等 dc.IntersectClipRect(client); } } CleanScreen(); for(int i=0;i td> DrawPoint(value[i],RGB(0,255,0)); dc.SelectObject(pOldBitmap); ReleaseDC(pDC); } if(nIDEvent==1) { m_dx+=2; for(int i=0;i td> { value[i].x=i; if(m_dx+i<0) buffer[m_dx+i]=128; if(m_dx+i<-m_DX) m_dx-=2; if(m_dx+i>m_BufLen) buffer[m_dx+i]=128; if(m_dx+i>m_BufLen+m_DX) m_dx-=2; value[i].y=m_DY*(buffer[m_dx+i]-128)/256; } } CFormView::OnTimer(nIDEvent); } (8)雖然通過上述幾步可以實(shí)現(xiàn)所有的功能,但為了防止內(nèi)存泄露和養(yǎng)成良好的編程習(xí)慣,,我們還須做些工作,,在視類的構(gòu)造函數(shù)中釋放我們?cè)?jīng)申請(qǐng)過的內(nèi)存以及定時(shí)器:
CWaveShowerView::~CWaveShowerView()
{ delete[] value; KillTimer(0); KillTimer(1); } 四、 編譯運(yùn)行
編譯運(yùn)行此程序,,通過菜單選取需要顯示的文件(任意文件均可),,如在定時(shí)器響應(yīng)代碼中采用的是第一種代碼,則會(huì)看到數(shù)據(jù)顯示的同時(shí)伴隨著明顯的閃爍而采用后一種代碼編碼則會(huì)很平穩(wěn)的將數(shù)據(jù)顯示出來,。 結(jié)論:本文介紹的這種方法適用于各種牽扯到數(shù)組數(shù)據(jù)圖形顯示的程序,,比如監(jiān)控軟件、數(shù)據(jù)分析軟件,、測(cè)量軟件等等,,具有廣泛的應(yīng)用前景
文章出處:飛諾網(wǎng)(www.):http://dev./course/3_program/vc/vc_js/20100630/266084.html |
|