VC 繪圖,使用雙緩沖技術(shù)實(shí)現(xiàn) |
發(fā)布時(shí)間: 2012/9/11 17:02:16 |
1. 首先定義類成員:CDC *m_pDC;CDC MemDC;CBitmap MemBitmap;CBitmap *pOldbitmap;LONG xRange; // 邏輯范圍,x方向?qū)挾萀ONG yRange; // 邏輯范圍,y方向高度LONG nWidht; // 物理范圍,x方向?qū)挾萀ONG nHeight; // 物理范圍,y方向高度2. 在類初始化函數(shù)中:m_pDC = this->GetDC(); // 獲取設(shè)備上下文句柄CWnd *wnd = GetDlgItem(IDC_SHOWGRAPH); // 獲取界面上顯示圖形的ID控件的句柄wnd->GetWindowRect(&rect); // 獲取顯示/畫(huà)圖區(qū)域大。ㄎ锢矸秶
ScreenToClient(&rect); // 轉(zhuǎn)換為客戶區(qū)坐標(biāo)nWidth = rect.Width(); // 顯示/畫(huà)圖區(qū)域x方向物理寬度nHeight = rect.Height(); // 顯示/畫(huà)圖區(qū)域y方向物理高度3. 在自定義函數(shù)中,設(shè)置視口與窗口的比例關(guān)系: m_pDC->SetMapMode(MM_ANISOTROPIC); // 注意MM_ANISOTROPIC和MM_ISOTROPIC的區(qū)別m_pDC->SetWindowExt(XRange,-yRange); // 設(shè)定窗口尺寸范圍,畫(huà)圖使用的邏輯范圍,實(shí)現(xiàn)放大或是縮小,坐標(biāo)方向↑和→為正向m_pDC->SetViewportExt(nWidth,nHeight); // 設(shè)定視口尺寸范圍,客戶區(qū)實(shí)際圖形顯示的區(qū)域范圍,大小固定m_pDC->SetViewportOrg(rect.left,rect.bottom); //設(shè)定畫(huà)圖的邏輯原點(diǎn)坐標(biāo)(0,0)在物理坐標(biāo)的(rect.left,rect.bottom)點(diǎn)上4. 在自定義函數(shù)中,雙緩沖技術(shù)的使用:MemDC.CreateCompatibleDC(m_pDC); // 創(chuàng)建內(nèi)存兼容設(shè)備上下文MemBitmap.CreateCompatibleBitmap(m_pDC,xRange,yRange); // 創(chuàng)建內(nèi)存兼容畫(huà)布,大小由邏輯范圍決定pOldbitmap = MemDC.SelectObject(&MemBitmap); // 將畫(huà)布選入內(nèi)存設(shè)備上下文MemDC.FillSolidRect(0,0,xRange,yRange,RGB(123,213,132)); // 對(duì)內(nèi)存中的畫(huà)布填充背景顏色,否則是默認(rèn)的黑色// 畫(huà)圖操作,如畫(huà)一條對(duì)角直線MemDC.MoveTo(0,0);MemDC.LineTo(xRange*0.9,yRange*0.9);// 將內(nèi)存中的畫(huà)圖區(qū)域拷貝到界面的控件區(qū)域上去// 第1和第2個(gè)參數(shù)若是0時(shí),則從物理坐標(biāo)的(rect.left,rect.bottom)點(diǎn)上開(kāi)始按上述指定的方向貼圖m_pDC->BitBlt(0,0,xRange,yRange,&MemDC,0,0,SRCCOPY);5. 在類的析構(gòu)函數(shù)中:MemDC.SelectObject(pOldbitmap);bitmap.DeleteObject();this->ReleaseDC(m_pDC);6. 至此,就完成了雙緩沖及坐標(biāo)縮放繪圖的功能*********************************************************************************************************************************************用VC做的畫(huà)圖程序,當(dāng)所畫(huà)的圖形大于屏幕時(shí),在拖動(dòng)滾動(dòng)條時(shí)屏幕就會(huì)出現(xiàn)嚴(yán)重的閃爍,為了解決這一問(wèn)題,就得使用雙緩沖來(lái)解決。程序產(chǎn)生嚴(yán)重的閃爍問(wèn)題是因?yàn)楫?huà)圖過(guò)程中前后兩次的畫(huà)面反差很大造成的人的視覺(jué)的閃爍。因?yàn)樵赩C中每次在調(diào)用OnDraw時(shí)系統(tǒng)都是先用背景畫(huà)刷將畫(huà)布清除再執(zhí)行畫(huà)圖命令,這樣在你每次移動(dòng)滾動(dòng)條時(shí)每執(zhí)行一次OnDraw就會(huì)有一個(gè)空白頁(yè),這樣和你的最終結(jié)果圖象之間有一個(gè)很大的反差,因而看起來(lái)閃爍,而且滾動(dòng)條滾動(dòng)越快閃爍越嚴(yán)重。當(dāng)然,你可以將背景畫(huà)刷設(shè)為NULL,這樣可以解決閃爍問(wèn)題,但是不能將先前的圖象擦除,這樣整個(gè)屏幕就顯得很亂。 下面將利用雙緩沖來(lái)解決這一問(wèn)題的思路給大家作一下簡(jiǎn)單的介紹。 我先來(lái)解釋一下在MFC里面很關(guān)鍵的設(shè)備環(huán)境描述符,也就是所謂的 DC(device context)。 在dos時(shí)代,我們?nèi)绻L圖,必須通過(guò)一系列系統(tǒng)函數(shù)來(lái)啟動(dòng)圖形環(huán)境(用過(guò)turbo pascal或者turbo c的人該還有印象吧),這之間對(duì)各種硬件的初始化參數(shù)都不相同,非常的煩人,常常還要查閱硬件手冊(cè),那時(shí)的程序智能針對(duì)最流行的硬件來(lái)編寫(xiě),對(duì)不流行的就沒(méi)有辦法了。windows操作系統(tǒng)為了屏蔽不同的硬件環(huán)境,讓編程時(shí)候不考慮具體的硬件差別,采取了一系列辦法,設(shè)備環(huán)境描述符就是這樣產(chǎn)生的。簡(jiǎn)單地說(shuō),設(shè)備描述符抽象了不同的硬件環(huán)境為標(biāo)準(zhǔn)環(huán)境,用戶編寫(xiě)時(shí)使用的是這個(gè)虛擬的標(biāo)準(zhǔn)環(huán)境,而不是真實(shí)的硬件,與真實(shí)硬件打交道的工作一般交給系統(tǒng)和驅(qū)動(dòng)程序去完成(這同樣解釋了為什么我們需要經(jīng)常更新驅(qū)動(dòng)程序的問(wèn)題)。使用在windows圖形系統(tǒng)(gdi,而不包括direct x)上面,就體現(xiàn)在一系列的圖形DC上面,我們?nèi)绻趃di上面繪圖,就必須先得到圖形DC的句柄(handle),然后在指定句柄的基礎(chǔ)上進(jìn)行圖形操作。 那么我們?cè)趺丛趕dk環(huán)境下面繪圖的呢,我想這個(gè)大家都不太清楚,但是確實(shí)很基礎(chǔ)。在windows的sdk環(huán)境下面,我們用傳統(tǒng)的c編寫(xiě)程序,在需要的繪圖地方(比如響應(yīng)WM_PAINT消息的分支)這樣做: hdc = GetDC( hwnd );oldGdiObject = SelectObject( hdc,newGdiObject );……繪圖操作…… SelectObject( hdc,oldGdiObject );DeleteObject( newGdiObject );ReleaseDC( hdc); 或者這樣 BeginPaint( hwnd,&ps ); //PAINTSTRUCT ps —— ps is a paint struct……繪圖操作…… EndPaint( hwnd ) 這就是大概的過(guò)程,我們看到了hdc(圖形DC句柄)的應(yīng)用。在繪圖的部分,每一個(gè)繪圖函數(shù)基本上也要用到這個(gè)句柄,最后我們還必須釋放它,否則將嚴(yán)重影響性能。每次我們都必須調(diào)用GetDC這個(gè)api函數(shù)得到(不能用全局變量保存結(jié)果重復(fù)使用,我在后面將做解釋)。這些是最最基本的windows圖形操作的方式,相比dos時(shí)代簡(jiǎn)單了些,但是有些概念也難理解了些。vb里面的簡(jiǎn)單的point函數(shù)其實(shí)最后也是被轉(zhuǎn)化為這樣的方式來(lái)執(zhí)行,系統(tǒng)幫助做了很多事情。 到了MFC里面,由于有了封裝,所有的hdc被隱藏在對(duì)象中做為隱藏參數(shù)來(lái)傳遞(就是DC類的this啦~~),所以我們的關(guān)鍵話題就轉(zhuǎn)變?yōu)榱嗽鯓拥玫较胍腄C類而已。這個(gè)過(guò)程其實(shí)大同小異。在消息響應(yīng)的過(guò)程中,WM_PAINT被轉(zhuǎn)變?yōu)镺nDraw()或是OnPaint()之類的一系列函數(shù)來(lái)響應(yīng),這些函數(shù)一般都有個(gè)參數(shù)CDC *pDC傳入進(jìn)來(lái),因此在這些函數(shù)里面,我們只需直接畫(huà)圖就可以了,和以前sdk的方式一樣。 但是WM_PAINT消息響應(yīng)的頻度太高了,比如最小化最大化,移動(dòng)窗體,覆蓋等等都引起重繪,經(jīng)常的這樣畫(huà)圖,很是消耗性能;在有些場(chǎng)合,比如隨機(jī)作圖的場(chǎng)合,每一次就改變,還導(dǎo)致了程序的無(wú)法實(shí)現(xiàn)。怎么解決后一種問(wèn)題呢。 ms在msdn的例子里面交給我們document/view的經(jīng)典解決辦法,將圖形的數(shù)據(jù)存儲(chǔ)在document類里面,view類只是根據(jù)這些數(shù)據(jù)繪圖。比如你要畫(huà)個(gè)圓,只是將圓心和半徑存在document里面,view類根據(jù)這個(gè)里面的數(shù)據(jù)在屏幕上面重新繪制。那么,我們只需要隨機(jī)產(chǎn)生一次數(shù)據(jù)就可以了。 這樣還是存在性能的問(wèn)題,于是我們開(kāi)始考慮另外的解決方法。我們知道,將內(nèi)存中的圖片原樣輸出到屏幕是很快的,這也是我們?cè)赿os時(shí)代經(jīng)常做的事情,能不能在windows也重新利用呢?答案就是內(nèi)存緩沖繪圖。這就是我們今天的主題。 我們還是回到DC上來(lái),既然DC是繪圖對(duì)象,我們也就可以自己在內(nèi)存里面造一個(gè),讓它等于我們想要的繪圖對(duì)象,圖(CBitmap)可以存儲(chǔ)在document類里面,每一次刷新屏幕都只需將這個(gè)圖輸出到屏幕上面,每一次作圖都是在內(nèi)存里面繪制,保存在document的圖里面,必要時(shí)還可以將圖輸出到外存保存。這樣既保證了速度,也解決了隨機(jī)的問(wèn)題,在復(fù)雜作圖的情況下對(duì)內(nèi)存的開(kāi)銷(xiāo)也不大(總是一副圖片的大。。這是一個(gè)很好的解決辦法,現(xiàn)在讓我們來(lái)實(shí)現(xiàn)它們。 1. 我們首先在document類里面保存一個(gè)圖片CBitmap m_bmpBuf; //這里面保存了我們做的圖,存在于內(nèi)存中 2. 其次在view類里面,我們需要將這個(gè)圖拷貝到屏幕上去,于OnDraw(CDC *pDC)函數(shù)中: CDC dcMem; // 以下是輸出位圖的標(biāo)準(zhǔn)操作CBitmap *pOldBitmap = NULL;dcMem.CreateCompatibleDC(NULL);pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);BITMAP bmpinfo;pDoc->m_bmpBuf.GetBitmap(&bmpinfo);pDC->BitBlt(0,0,bmpinfo.bmWidth,bmpinfo.bmHeight,&dcMem,0,0,SRCCOPY);dcMem.SelectObject(pOldBitmap);dcMem.DeleteDC(); 3. 在我們需要畫(huà)圖的函數(shù)里,完成繪圖工作 CBmpDrawDoc *pDoc = GetDocument(); // 得到document中的bitmap對(duì)象CDC *pDC = GetDC();CDC dcMem;dcMem.CreateCompatibleDC(NULL); // 這里我們就在內(nèi)存中虛擬建造了DC pDoc->m_bmpBuf.DeleteObject();pDoc->m_bmpBuf.CreateCompatibleBitmap(pDC,100,100); // 依附DC創(chuàng)建bitmap CBitmap *pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf); // 調(diào)入了我們的bitmap目標(biāo) dcMem.FillSolidRect(0,0,100,100,RGB(255,255,255)); // 這些是繪圖操作,隨便你^_^ dcMem.TextOut(0,0,"Hello,world!");dcMem.Rectangle(20,20,40,40);dcMem.FillSolidRect(40,40,50,50,RGB(255,0,0)); pDC->BitBlt(0,0,100,100,&dcMem,0,0,SRCCOPY); // 拷貝到屏幕dcMem.SelectObject(pOldBitmap);dcMem.DeleteDC(); 全部的過(guò)程就是這樣,很簡(jiǎn)單吧。以此為例子還可以實(shí)現(xiàn)2個(gè)緩沖或者多個(gè)緩沖等等,視具體情況而定。當(dāng)然在緩沖區(qū)還可以實(shí)現(xiàn)很多高級(jí)的圖形操作,比如透明,合成等等,取決于具體的算法,需要對(duì)內(nèi)存直接操作(其實(shí)就是當(dāng)年dos怎么做,現(xiàn)在還怎么做)。 再來(lái)解釋一下前面說(shuō)的為什么不能用全局變量保存DC問(wèn)題:其實(shí)DC也是用句柄來(lái)標(biāo)識(shí)的,所以也具有句柄的不確定性,就是只能隨用隨取,不同時(shí)間兩次取得的是不同的(使用過(guò)文件句柄地話,應(yīng)該很容易理解的)。那么我們用全局變量保存的DC就沒(méi)什么意義了,下次使用只是什么也畫(huà)不出來(lái)。(這一點(diǎn)的理解可以這樣:DC需要占用一定的內(nèi)存,那么在頻繁的頁(yè)面調(diào)度中,位置難免改變,于是用來(lái)標(biāo)志指針的句柄也就不同了)。 *********************************************************************************************************************************************顯示圖形如何避免閃爍 顯示圖形如何避免閃爍,如何提高顯示效率是問(wèn)得比較多的問(wèn)題。而且多數(shù)人認(rèn)為MFC的繪圖函數(shù)效率很低,總是想尋求其它的解決方案。MFC的繪圖效率的確不高但也不差,而且它的繪圖函數(shù)使用非常簡(jiǎn)單,只要使用方法得當(dāng),再加上一些技巧,用MFC可以得到效率很高的繪圖程序。 我想就我長(zhǎng)期(呵呵當(dāng)然也只有2年多)使用MFC繪圖的經(jīng)驗(yàn)談?wù)勎业囊恍┯^點(diǎn)。 1、顯示的圖形為什么會(huì)閃爍? 我們的繪圖過(guò)程大多放在OnDraw或者OnPaint函數(shù)中,OnDraw在進(jìn)行屏幕顯示時(shí)是由OnPaint進(jìn)行調(diào)用的。當(dāng)窗口由于任何原因需要重繪時(shí),總是先用背景色將顯示區(qū)清除,然后才調(diào)用OnPaint,而背景色往往與繪圖內(nèi)容反差很大,這樣在短時(shí)間內(nèi)背景色與顯示圖形的交替出現(xiàn),使得顯示窗口看起來(lái)在閃。如果將背景刷設(shè)置成NULL,這樣無(wú)論怎樣重繪圖形都不會(huì)閃了。 當(dāng)然,這樣做會(huì)使得窗口的顯示亂成一團(tuán),因?yàn)橹乩L時(shí)沒(méi)有背景色對(duì)原來(lái)繪制的圖形進(jìn)行清除,而又疊加上了新的圖形。 有的人會(huì)說(shuō),閃爍是因?yàn)槔L圖的速度太慢或者顯示的圖形太復(fù)雜造成的,其實(shí)這樣說(shuō)并不對(duì),繪圖的顯示速度對(duì)閃爍的影響不是根本性的。 例如在OnDraw(CDC *pDC)中這樣寫(xiě):pDC->MoveTo(0,0);pDC->LineTo(100,100);這個(gè)繪圖過(guò)程應(yīng)該是非常簡(jiǎn)單、非?炝税桑抢瓌(dòng)窗口變化時(shí)還是會(huì)看見(jiàn)閃爍。其實(shí)從道理上講,畫(huà)圖的過(guò)程越復(fù)雜越慢閃爍應(yīng)該越少,因?yàn)槔L圖用的時(shí)間與用背景清除屏幕所花的時(shí)間的比例越大人對(duì)閃爍的感覺(jué)會(huì)越不明顯。比如:清楚屏幕時(shí)間為1s繪圖時(shí)間也是為1s,這樣在10s內(nèi)的連續(xù)重畫(huà)中就要閃爍5次;如果清楚屏幕時(shí)間為1s不變,而繪圖時(shí)間為9s,這樣10s內(nèi)的連續(xù)重畫(huà)只會(huì)閃爍一次。這個(gè)也可以試驗(yàn),在OnDraw(CDC *pDC)中這樣寫(xiě):for(int i=0;i<100000;i++) { pDC->MoveTo(0,i);pDC->LineTo(1000,i);}呵呵,程序有點(diǎn)變態(tài),但是能說(shuō)明問(wèn)題。 說(shuō)到這里可能又有人要說(shuō)了,為什么一個(gè)簡(jiǎn)單圖形看起來(lái)沒(méi)有復(fù)雜圖形那么閃呢?這是因?yàn)閺?fù)雜圖形占的面積大,重畫(huà)時(shí)造成的反差比較大,所以感覺(jué)上要閃得厲害一些,但是閃爍頻率要低。那為什么動(dòng)畫(huà)的重畫(huà)頻率高,而看起來(lái)卻不閃?這里,我就要再次強(qiáng)調(diào)了,閃爍是什么?閃爍就是反差,反差越大,閃爍越厲害。因?yàn)閯?dòng)畫(huà)的連續(xù)兩個(gè)幀之間的差異很小所以看起來(lái)不閃。如果不信,可以在動(dòng)畫(huà)的每一幀中間加一張純白的幀,不閃才怪呢。 2、如何避免閃爍在知道圖形顯示閃爍的原因之后,對(duì)癥下藥就好辦了。 。1)。 首先是去掉MFC 提供的背景繪制過(guò)程。實(shí)現(xiàn)的方法很多:* 可以在窗口形成時(shí)給窗口的注冊(cè)類的背景刷賦NULL * 也可以在形成以后修改背景static CBrush brush(RGB(255,0,0));SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);* 要簡(jiǎn)單也可以重載OnEraseBkgnd(CDC* pDC)直接返回TRUE這樣背景沒(méi)有了,結(jié)果圖形顯示的確不閃了,但是顯示也象前面所說(shuō)的一樣,變得一團(tuán)亂。怎么辦? 。2)。 這就要用到雙緩存的方法了。雙緩沖就是除了在屏幕上有圖形進(jìn)行顯示以外,在內(nèi)存中也有圖形在繪制。我們可以把要顯示的圖形先在內(nèi)存中繪制好,然后再一次性的將內(nèi)存中的圖形按照一個(gè)點(diǎn)一個(gè)點(diǎn)地覆蓋到屏幕上去(這個(gè)過(guò)程非常快,因?yàn)槭欠浅R?guī)整的內(nèi)存拷貝)。這樣在內(nèi)存中繪圖時(shí),隨便用什么反差大的背景色進(jìn)行清除都不會(huì)閃,因?yàn)榭床灰?jiàn)。當(dāng)貼到屏幕上時(shí),因?yàn)閮?nèi)存中最終的圖形與屏幕顯示圖形差別很。ㄈ绻麤](méi)有運(yùn)動(dòng),當(dāng)然就沒(méi)有差別),這樣看起來(lái)就不會(huì)閃。 3、如何實(shí)現(xiàn)雙緩沖首先給出實(shí)現(xiàn)的程序,然后再解釋,同樣是在OnDraw(CDC *pDC)中: CRect rc; // 定義一個(gè)矩形區(qū)域變量GetClientRect(rc);int nWidth = rc.Width();int nHeight = rc.Height(); CDC *pDC = GetDC(); // 定義設(shè)備上下文CDC MemDC; // 定義一個(gè)內(nèi)存顯示設(shè)備對(duì)象CBitmap MemBitmap; // 定義一個(gè)位圖對(duì)象 //建立與屏幕顯示兼容的內(nèi)存顯示設(shè)備MemDC.CreateCompatibleDC(pDC);//建立一個(gè)與屏幕顯示兼容的位圖,位圖的大小可選用窗口客戶區(qū)的大小MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);//將位圖選入到內(nèi)存顯示設(shè)備中,只有選入了位圖的內(nèi)存顯示設(shè)備才有地方繪圖,畫(huà)到指定的位圖上CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);//先用背景色將位圖清除干凈,否則是黑色。這里用的是白色作為背景MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255)); //繪圖操作等在這里實(shí)現(xiàn)MemDC.MoveTo(……);MemDC.LineTo(……);MemDC.Ellipse(……); //將內(nèi)存中的圖拷貝到屏幕上進(jìn)行顯示pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY); //繪圖完成后的清理MemDC.SelectObject(pOldbitmap);MemBitmap.DeleteObject(); 上面的注釋?xiě)?yīng)該很詳盡了,廢話就不多說(shuō)了。 4、如何提高繪圖的效率我主要做的是電力系統(tǒng)的網(wǎng)絡(luò)圖形的CAD軟件,在一個(gè)窗口中往往要顯示成千上萬(wàn)個(gè)電力元件,而每個(gè)元件又是由點(diǎn)、線、圓等基本圖形構(gòu)成。如果真要在一次重繪過(guò)程重畫(huà)這么多元件,可想而知這個(gè)過(guò)程是非常漫長(zhǎng)的。如果加上了圖形的瀏覽功能,鼠標(biāo)拖動(dòng)圖形滾動(dòng)時(shí)需要進(jìn)行大量的重繪,速度會(huì)慢得讓用戶將無(wú)法忍受。怎么辦?只有再研究研究MFC的繪圖過(guò)程了。 實(shí)際上,在OnDraw(CDC *pDC)中繪制的圖并不是所有都顯示了的,例如:你在OnDraw中畫(huà)了兩個(gè)矩形,在一次重繪中雖然兩個(gè)矩形的繪制函數(shù)都有執(zhí)行,但是很有可能只有一個(gè)顯示了,這是因?yàn)镸FC本身為了提高重繪的效率設(shè)置了裁剪區(qū)。裁剪區(qū)的作用就是:只有在這個(gè)區(qū)內(nèi)的繪圖過(guò)程才會(huì)真正有效,在區(qū)外的是無(wú)效的,即使在區(qū)外執(zhí)行了繪圖函數(shù)也是不會(huì)顯示的。因?yàn)槎鄶?shù)情況下窗口重繪的產(chǎn)生大多是因?yàn)榇翱诓糠直徽趽趸蛘叽翱谟袧L動(dòng)發(fā)生,改變的區(qū)域并不是整個(gè)圖形而只有一小部分,這一部分需要改變的就是pDC中的裁剪區(qū)了。因?yàn)轱@示(往內(nèi)存或者顯存都叫顯示)比繪圖過(guò)程的計(jì)算要費(fèi)時(shí)得多,有了裁剪區(qū)后顯示的就只是應(yīng)該顯示的部分,大大提高了顯示效率。但是這個(gè)裁剪區(qū)是MFC設(shè)置的,它已經(jīng)為我們提高了顯示效率,在進(jìn)行復(fù)雜圖形的繪制時(shí)如何進(jìn)一步提高效率呢?那就只有去掉在裁剪區(qū)外的繪圖過(guò)程了。可以先用pDC->GetClipBox()得到裁剪區(qū),然后在繪圖時(shí)判斷你的圖形是否在這個(gè)區(qū)內(nèi),如果在就畫(huà),不在就不畫(huà)。但如果你的繪圖過(guò)程不復(fù)雜,這樣做可能對(duì)你的繪圖效率不會(huì)有提高。 *********************************************************************************************************************************************雙緩存即現(xiàn)在內(nèi)存dc中作圖,而后一次性地拷貝到屏幕上,所以提高了繪圖的速度。但只用此方法不能根本解決閃爍的問(wèn)題。 而將響應(yīng) WM_ERASEBKGND 的重載函數(shù) OnEraseBkgnd(CDC* pDC) 直接返回TRUE是最好的辦法。 如下:BOOL CMyWin::OnEraseBkgnd(CDC* pDC) { return TRUE;//return CWnd::OnEraseBkgnd(pDC); //把系統(tǒng)原來(lái)的這條語(yǔ)句注釋掉} *********************************************************************************************************************************************如何修改控件的背景模式及控件的字體顏色1. 改變對(duì)話框的背景色在C…App類中的InitInstance()里添加SetDialogBkColor(RGB(0,192,0),RGB(0,0,0)); 2. 如果想改變靜態(tài)文本或單選按鈕的背景色,首先需要獲得控件ID,然后設(shè)置背景色,具體步驟:(1) 響應(yīng)對(duì)話框類的WM_CTLCOLOR消息,生成OnCtlColor函數(shù)(2) 為對(duì)話框類添加成員變量CBrush m_brush;并在初始化函數(shù)中初始化m_brush.CreateSolidBrush(RGB(0,255,0)); //顏色在這里設(shè)置(3) 在OnCtlColor函數(shù)中添加代碼,以改變控件的文字顏色和背景色switch(pWnd->GetDlgCtrlID()) { case(IDC_INPUT):pDC->SetTextColor(RGB(255,0,192));pDC->SetBkMode(TRANSPARENT);return m_brush;break;case(IDC_EDIT):pDC->SetTextColor(RGB(255,0,0));pDC->SetBkMode(TRANSPARENT);return m_brush;break;case(IDC_CHOICE):pDC->SetTextColor(RGB(255,128,0));pDC->SetBkMode(TRANSPARENT);return m_brush;break;case(IDC_RADIO):pDC->SetTextColor(RGB(255,0,20));pDC->SetBkMode(TRANSPARENT);return m_brush;break;default:break;} ******************************************************************************************************************************************* OnEraseBkGnd與OnPaint的聯(lián)系是什么? 轉(zhuǎn)自:http://topic.csdn.net/u/20091012/14/2b948708-6d7b-498a-9806-a2adbd000c5d.html (作者:Tr0j4n) 系統(tǒng)重繪時(shí),先調(diào)用OnEraseBkGnd擦除窗口的現(xiàn)有內(nèi)容,再調(diào)用OnPaint繪制新內(nèi)容。 問(wèn)題就產(chǎn)生的:在OnEraseBkGnd中,如果你不調(diào)用原來(lái)缺省的OnEraseBkGnd只是重畫(huà)背景則不會(huì)有閃爍。而在OnPaint里面,由于它隱含的調(diào)用了OnEraseBkGnd,而你又沒(méi)有處理OnEraseBkGnd函數(shù),這時(shí)就和窗口缺省的背景刷相關(guān)了。缺省的OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情況下是白刷),而隨后你又自己重畫(huà)背景造成屏幕閃動(dòng)。 另外的一個(gè)問(wèn)題是OnEraseBkGnd不是每次都會(huì)被調(diào)用的。如果你調(diào)用Invalidate的時(shí)候參數(shù)為T(mén)RUE,那么在OnPaint里面隱含調(diào)用BeginPaint的時(shí)候就產(chǎn)生WM_ERASEBKGND消息,如果參數(shù)是FALSE 則不會(huì)重刷背景。 解決方法有:1. 用OnEraseBkGnd實(shí)現(xiàn),不要調(diào)用原來(lái)的OnEraseBkGnd函數(shù)。 2. 用OnPaint實(shí)現(xiàn),同時(shí)重載OnEraseBkGnd,并在其中直接返回TRUE. 3. 用OnPaint實(shí)現(xiàn),創(chuàng)建窗口時(shí)設(shè)置背景刷為空。 4. 用OnPaint實(shí)現(xiàn),但是要求刷新時(shí)用Invalidate(FALSE)這樣的函數(shù)。(不過(guò)這種情況下,窗口覆蓋等造成的刷新還是要閃一下,所以不是徹底的解決方法) -------------------------------------------------------------------------------------------------------------------------------- 在MFC中任何一個(gè)window組件的繪圖都是放在這兩個(gè)member function中。在設(shè)定上OnEraseBkgnd()是用來(lái)畫(huà)底圖的,而OnPaint()是用來(lái)畫(huà)主要對(duì)象的。 舉例說(shuō)明,一個(gè)按鈕是灰色的,上面還有文字。則OnEraseBkgnd()所做的事就是把按鈕畫(huà)成灰色,而OnPaint()所做的事就是畫(huà)上文字。 既然這兩個(gè)member function都是用來(lái)畫(huà)出組件的,那為何還要分OnPaint() 與 OnEraseBkgnd() 呢? 其實(shí)OnPaint() 與 OnEraseBkgnd() 特性是有差別的:1. OnEraseBkgnd()的要求是快速,在里面的繪圖程序最好是不要太耗時(shí)間,因?yàn)槊慨?dāng)window組件有任何小變動(dòng)都會(huì)馬上呼叫OnEraseBkgnd() . 2. OnPaint() 是只有在程序有空閑的時(shí)候才會(huì)被呼叫。 3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的。 所以 OnPaint() 被呼叫一次之前。可能會(huì)呼叫OnEraseBkgnd()好幾次。 如果我們是一個(gè)在做圖形化使用者接口的人,常會(huì)需要把一張美美的圖片設(shè)為我們dialog的底圖。把繪圖的程序代碼放在OnPaint() 之中,可能會(huì)常碰到一些問(wèn)題。比方說(shuō)拖曳一個(gè)窗口在我們做的dialog上面一直移動(dòng),則dialog會(huì)變成灰色,直到動(dòng)作停止才恢復(fù)。這是因?yàn)槊看涡枰乩L的時(shí)候,程序都會(huì)馬上呼叫OnEraseBkgnd()。而OnEraseBkgnd()就把dialog畫(huà)成灰色,只有在動(dòng)作停止之后,程序才會(huì)呼叫OnPaint(),這時(shí)才會(huì)把我們要畫(huà)的底圖貼上去。 這個(gè)問(wèn)題的解法:1. 比較差點(diǎn)的方法是把OnEraseBkgnd() 改寫(xiě)成不做事的function ,如下所示:BOOL CMyDlg::OnEraseBkgnd(CDC* pDC) { return TRUE;}以上本來(lái)是會(huì)呼叫CDialog::OnEraseBkgnd() ,但是如果我們不呼叫的話,程序便不會(huì)畫(huà)上灰色的底色了。 2. 比較好的做法是,直接將繪圖的程序從OnPaint()移到OnEraseBkgnd()來(lái)做,如下所示 : // m_bmpBKGND 為一CBitmap對(duì)象,且事先早已加載我們的底圖// 底圖的大小與我們的窗口client大小一致 BOOL CMyDlg::OnEraseBkgnd(CDC* pDC) { CRect rc;GetUpdateRect(&rc);CDC srcDC;srcDC.CreateCompatibleDC(pDC);srcDC.SelectObject(m_bmpBKGND); pDC->BitBlt(rc.left,rc.top,rc.GetWidth(), rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);return TRUE;} 特別要注意的是,取得重畫(huà)大小是使用GetUpdateRect() 而不是GetClientRect()。如果使用GetClientRect() 則會(huì)把不該重畫(huà)的地方重畫(huà)。 *****************************************************************************************************************************************雙緩沖加重載onpaint,OnEraseBkgnd解決屏幕閃爍問(wèn)題轉(zhuǎn)自:http://hi.baidu.com/lovevc2008/blog/item/9bc5a90b2a3eab1894ca6b0e.html自己實(shí)現(xiàn)了按鈕切換背景功能后,正暗自爽的我發(fā)現(xiàn)了一個(gè)很?chē)?yán)重的問(wèn)題。背景切換時(shí)總是先出現(xiàn)mfc自帶的灰色難看界面才刷出我用form image控件載入的圖片。上網(wǎng)google了很久?偹闶墙鉀Q我自己的問(wèn)題。 分三步走:第一, 在OnInitDialog中寫(xiě)入//////////載入背景圖if( m_bmp.m_hObject != NULL ) //判斷m_bmp.DeleteObject();/////////載入圖片HBITMAP hbmp = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),"res\\aaaaa.BMP", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION|LR_LOADFROMFILE);if( hbmp == NULL ) return FALSE;///////////////////////該斷程序用來(lái)取得加載的BMP的信息//////////////////////// m_bmp.Attach( hbmp );DIBSECTION ds;BITMAPINFOHEADER &bminfo = ds.dsBmih;m_bmp.GetObject( sizeof(ds), &ds );int cx=bminfo.biWidth; //得到圖像寬度int cy=bminfo.biHeight; //得到圖像高度/////得到了圖像的寬度和高度后,我們就可以對(duì)圖像大小進(jìn)行適應(yīng),即調(diào)整控件的大小,讓它正好顯示一張圖片///// CRect rect;GetDlgItem(IDC_BAK)->GetWindowRect(&rect);ScreenToClient(&rect);GetDlgItem(IDC_BAK)->MoveWindow(rect.left,rect.top,cx,cy,true);//調(diào)整大小第二,重載onpaint函數(shù)//////////////以下三種情況任選一種會(huì)是不同效果(只能一種存在)/////////// //CPaintDC dc(this); //若用此句,得到的是對(duì)話框的DC,圖片將被繪制在對(duì)話框上。 CPaintDC dc(GetDlgItem(IDC_BAK)); // 用此句,得到picture控件的DC,圖像將被繪制在控件上// CDC dc;// dc.m_hDC=::GetDC(NULL); //若用此兩句,得到的是屏幕的DC,圖片將被繪制在屏幕上////////// CRect rcclient;GetDlgItem(IDC_BAK)->GetClientRect(&rcclient);CDC memdc;// Step 1: 為屏幕DC創(chuàng)建兼容的內(nèi)存DC : CreateCompatibleDC() memdc.CreateCompatibleDC(&dc);CBitmap bitmap;bitmap.CreateCompatibleBitmap(&dc, rcclient.Width(), rcclient.Height());// Step 2: 把位圖選入設(shè)備環(huán)境:SelectObject(),可以理解為選擇畫(huà)布memdc.SelectObject( &bitmap );CWnd::DefWindowProc(WM_PAINT, (WPARAM)memdc.m_hDC , 0);CDC maskdc;maskdc.CreateCompatibleDC(&dc);CBitmap maskbitmap;maskbitmap.CreateBitmap(rcclient.Width(), rcclient.Height(), 1, 1, NULL);maskdc.SelectObject( &maskbitmap );maskdc.BitBlt( 0, 0, rcclient.Width(), rcclient.Height(), &memdc, rcclient.left, rcclient.top, SRCCOPY);CBrush brush;brush.CreatePatternBrush(&m_bmp);dc.FillRect(rcclient, &brush);// Step 3: 把繪制好的圖形“拷貝”到屏幕上: BitBlt() dc.BitBlt(rcclient.left, rcclient.top, rcclient.Width(), rcclient.Height(), &memdc, rcclient.left, rcclient.top,SRCPAINT);brush.DeleteObject();// Do not call CDialog::OnPaint() for painting messages第三,重載OnEraseBkgnd改為 return TRUE; // CDialog::OnEraseBkgnd(pDC);*******************************************************************************************************************************************解決Windows 程序界面閃爍問(wèn)題的一些經(jīng)驗(yàn)轉(zhuǎn)載自:http://blog.joycode.com/yaodong/archive/2004/11/26/39764.joy一般的windows 復(fù)雜的界面需要使用多層窗口而且要用貼圖來(lái)美化,所以不可避免在窗口移動(dòng)或者改變大小的時(shí)候出現(xiàn)閃爍。 先來(lái)談?wù)勯W爍產(chǎn)生的原因原因一:如果熟悉顯卡原理的話,調(diào)用GDI函數(shù)向屏幕輸出的時(shí)候并不是立刻就顯示在屏幕上只是寫(xiě)到了顯存里,而顯卡每隔一段時(shí)間把顯存的內(nèi)容輸出到屏幕上,這就是刷新周期。 一般顯卡的刷新周期是1/80秒左右,具體數(shù)字可以自己設(shè)置的。 這樣問(wèn)題就來(lái)了,一般畫(huà)圖都是先畫(huà)背景色,然后再把內(nèi)容畫(huà)上去,如果這兩次操作不在同一個(gè)刷新周期內(nèi)完成,那么給人的視覺(jué)感受就是,先看到只有背景色的圖像,然后看到畫(huà)上內(nèi)容的圖像,這樣就會(huì)感覺(jué)閃爍了。 解決方法:盡量快的輸出圖像,使輸出在一個(gè)刷新周期內(nèi)完成,如果輸出內(nèi)容很多比較慢,那么采用內(nèi)存緩沖的方法,先把要輸出的內(nèi)容在內(nèi)存準(zhǔn)備好,然后一次輸出到顯存。要知道一次API調(diào)用一般可以在一個(gè)刷新周期內(nèi)完成。 對(duì)于GDI,用創(chuàng)建內(nèi)存DC的方法就可以了。 原因二:復(fù)雜的界面有多層窗口組成,當(dāng)windows在窗口改變大小的時(shí)候是先重畫(huà)父窗口,然后重畫(huà)子窗口,子父窗口重畫(huà)的過(guò)程一般無(wú)法在一個(gè)刷新周期內(nèi)完成,所以會(huì)呈現(xiàn)閃爍。 我們知道父窗口上被子窗口擋住的部分其實(shí)沒(méi)必要重畫(huà)的。 解決方法:給窗口加個(gè)風(fēng)格 WS_CLIPCHILDREN ,這樣父窗口上被子窗口擋住的部分就不會(huì)重畫(huà)了。如果同級(jí)窗口之間有重疊,那么需要再加上 WS_CLIPSIBLINGS 風(fēng)格。 原因三:有時(shí)需要在窗口上使用一些控件,比如IE,當(dāng)你的窗口改變大小的時(shí)候IE會(huì)閃爍,即使你有了WS_CLIPCHILDREN也沒(méi)用。原因在于窗口的類風(fēng)格有CS_HREDRAW 或者 CS_VREDRAW,這兩個(gè)風(fēng)格表示窗口在寬度或者高度變化的時(shí)候重畫(huà),但是這樣就會(huì)引起IE閃爍。 解決方法:注冊(cè)窗口類的時(shí)候不要使用這兩個(gè)風(fēng)格,如果窗口需要在改變大小的時(shí)候重畫(huà),那么可以在WM_SIZE的時(shí)候調(diào)用RedrawWindow.原因四:界面上窗口很多,而且改變大小時(shí)很多窗口都要移動(dòng)和改變大小,如果使用MoveWindow或者SetWindowPos兩個(gè)API來(lái)改變窗口的大小和位置,由于他們是等待窗口重畫(huà)完成后才返回,所以過(guò)程很慢,這樣視覺(jué)效果就可能會(huì)閃爍。 解決方法:使用以下API來(lái)處理窗口移動(dòng),BeginDeferWindowPos, DeferWindowPos,EndDeferWindowPos.先調(diào)用 BeginDeferWindowPos 來(lái)設(shè)定需要移動(dòng)的窗口的個(gè)數(shù),在使用 DeferWindowPos 來(lái)移動(dòng)窗口,這個(gè)API并不真的造成窗口移動(dòng),最后用 EndDeferWindowPos 一次性完成所有窗口的大小和位置的改變。 有個(gè)地方要特別注意,要仔細(xì)計(jì)算清楚要移動(dòng)多少個(gè)窗口,BeginDeferWindowPos設(shè)定的個(gè)數(shù)一定要和實(shí)際的個(gè)數(shù)一致,否則在Win9x下,如果實(shí)際移動(dòng)的窗口數(shù)多于調(diào)用BeginDeferWindowPos時(shí)設(shè)定的個(gè)數(shù),可能會(huì)造成系統(tǒng)崩潰。在Windows NT系列下不會(huì)有這樣的問(wèn)題。 本文出自:億恩科技【1tcdy.com】 服務(wù)器租用/服務(wù)器托管中國(guó)五強(qiáng)!虛擬主機(jī)域名注冊(cè)頂級(jí)提供商!15年品質(zhì)保障!--億恩科技[ENKJ.COM] |