VC 繪圖,使用雙緩衝技術實現 - Cloud-Datacenter-Renewable Energy-Big Data-Model - 博客頻道 - CSDN.NEThtml
*******************************************************************例子*******************************************************************編程
********************全部的GDI繪圖函數使用的都是邏輯座標(邏輯範圍)*******************
*******************系統默認狀況下 物理範圍和邏輯範圍 是1:1 的對應關係*******************
1. 首先定義類成員:windowsCDC *m_pDC;
CDC MemDC;
CBitmap MemBitmap;
CBitmap *pOldbitmap;apiLONG xRange; // 邏輯範圍,x方向寬度
LONG yRange; // 邏輯範圍,y方向高度
LONG nWidht; // 物理範圍,x方向寬度
LONG nHeight; // 物理範圍,y方向高度緩存2. 在類初始化函數中:網絡
m_pDC = this->GetDC(); // 獲取設備上下文句柄 函數
CWnd *wnd = GetDlgItem(IDC_SHOWGRAPH); // 獲取界面上顯示圖形的ID控件的句柄
wnd->GetWindowRect(&rect); // 獲取顯示/畫圖區域大小(物理範圍)
ScreenToClient(&rect); // 轉換爲客戶區座標性能nWidth = rect.Width(); // 顯示/畫圖區域x方向物理寬度
nHeight = rect.Height(); // 顯示/畫圖區域y方向物理高度字體3. 在自定義函數中,設置視口與窗口的比例關係:
m_pDC->SetMapMode(MM_ANISOTROPIC); // 注意MM_ANISOTROPIC和MM_ISOTROPIC的區別m_pDC->SetWindowExt(XRange,-yRange); // 設定窗口尺寸範圍,畫圖使用的邏輯範圍,實現放大或是縮小,座標方向↑和→爲正向
m_pDC->SetViewportExt(nWidth,nHeight); // 設定視口尺寸範圍,客戶區實際圖形顯示的區域範圍,大小固定
m_pDC->SetViewportOrg(rect.left,rect.bottom); //設定畫圖的邏輯原點座標(0,0)在物理座標的(rect.left,rect.bottom)點上
4. 在自定義函數中,雙緩衝技術的使用:
MemDC.CreateCompatibleDC(m_pDC); // 建立內存兼容設備上下文
MemBitmap.CreateCompatibleBitmap(m_pDC,xRange,yRange); // 建立內存兼容畫布,大小由邏輯範圍決定
pOldbitmap = MemDC.SelectObject(&MemBitmap); // 將畫布選入內存設備上下文MemDC.FillSolidRect(0,0,xRange,yRange,RGB(123,213,132)); // 對內存中的畫布填充背景顏色,不然是默認的黑色
// 畫圖操做,如畫一條對角直線
MemDC.MoveTo(0,0);
MemDC.LineTo(xRange*0.9,yRange*0.9);// 將內存中的畫圖區域拷貝到界面的控件區域上去
// 第1和第2個參數如果0時,則從物理座標的(rect.left,rect.bottom)點上開始按上述指定的方向貼圖
m_pDC->BitBlt(0,0,xRange,yRange,&MemDC,0,0,SRCCOPY);5. 在類的析構函數中:
MemDC.SelectObject(pOldbitmap);
bitmap.DeleteObject();this->ReleaseDC(m_pDC);
6. 至此,就完成了雙緩衝及座標縮放繪圖的功能
*********************************************************************************************************************************************
用VC作的畫圖程序,當所畫的圖形大於屏幕時,在拖動滾動條時屏幕就會出現嚴重的閃爍,爲了解決這一問題,就得使用雙緩衝來解決。程序產生嚴重的閃爍問題是由於畫圖過程當中先後兩次的畫面反差很大形成的人的視覺的閃爍。由於在VC中每次在調用OnDraw時系統都是先用背景畫刷將畫布清除再執行畫圖命令,這樣在你每次移動滾動條時每執行一次OnDraw就會有一個空白頁,這樣和你的最終結果圖象之間有一個很大的反差,於是看起來閃爍,並且滾動條滾動越快閃爍越嚴重。固然,你能夠將背景畫刷設爲NULL,這樣能夠解決閃爍問題,可是不能將先前的圖象擦除,這樣整個屏幕就顯得很亂。
下面將利用雙緩衝來解決這一問題的思路給你們做一下簡單的介紹。
我先來解釋一下在MFC裏面很關鍵的設備環境描述符,也就是所謂的 DC(device context)。
在dos時代,咱們若是要繪圖,必須經過一系列系統函數來啓動圖形環境(用過turbo pascal或者turbo c的人該還有印象吧),這之間對各類硬件的初始化參數都不相同,很是的煩人,經常還要查閱硬件手冊,那時的程序智能針對最流行的硬件來編寫,對不流行的就沒有辦法了。windows操做系統爲了屏蔽不一樣的硬件環境,讓編程時候不考慮具體的硬件差異,採起了一系列辦法,設備環境描述符就是這樣產生的。簡單地說,設備描述符抽象了不一樣的硬件環境爲標準環境,用戶編寫時使用的是這個虛擬的標準環境,而不是真實的硬件,與真實硬件打交道的工做通常交給系統和驅動程序去完成(這一樣解釋了爲何咱們須要常常更新驅動程序的問題)。使用在windows圖形系統(gdi,而不包括direct x)上面,就體如今一系列的圖形DC上面,咱們若是要在gdi上面繪圖,就必須先獲得圖形DC的句柄(handle),而後在指定句柄的基礎上進行圖形操做。
那麼咱們怎麼在sdk環境下面繪圖的呢,我想這個你們都不太清楚,可是確實很基礎。在windows的sdk環境下面,咱們用傳統的c編寫程序,在須要的繪圖地方(好比響應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 )
這就是大概的過程,咱們看到了hdc(圖形DC句柄)的應用。在繪圖的部分,每個繪圖函數基本上也要用到這個句柄,最後咱們還必須釋放它,不然將嚴重影響性能。每次咱們都必須調用GetDC這個api函數獲得(不能用全局變量保存結果重複使用,我在後面將作解釋)。這些是最最基本的windows圖形操做的方式,相比dos時代簡單了些,可是有些概念也難理解了些。vb裏面的簡單的point函數其實最後也是被轉化爲這樣的方式來執行,系統幫助作了不少事情。
到了MFC裏面,因爲有了封裝,全部的hdc被隱藏在對象中作爲隱藏參數來傳遞(就是DC類的this啦~~),因此咱們的關鍵話題就轉變爲了怎樣獲得想要的DC類而已。這個過程其實大同小異。在消息響應的過程當中,WM_PAINT被轉變爲OnDraw()或是OnPaint()之類的一系列函數來響應,這些函數通常都有個參數CDC *pDC傳入進來,所以在這些函數裏面,咱們只需直接畫圖就能夠了,和之前sdk的方式同樣。
可是WM_PAINT消息響應的頻度過高了,好比最小化最大化,移動窗體,覆蓋等等都引發重繪,常常的這樣畫圖,非常消耗性能;在有些場合,好比隨機做圖的場合,每一次就改變,還致使了程序的沒法實現。怎麼解決後一種問題呢。
ms在msdn的例子裏面交給咱們document/view的經典解決辦法,將圖形的數據存儲在document類裏面,view類只是根據這些數據繪圖。好比你要畫個圓,只是將圓心和半徑存在document裏面,view類根據這個裏面的數據在屏幕上面從新繪製。那麼,咱們只須要隨機產生一次數據就能夠了。
這樣仍是存在性能的問題,因而咱們開始考慮另外的解決方法。咱們知道,將內存中的圖片原樣輸出到屏幕是很快的,這也是咱們在dos時代常常作的事情,能不能在windows也從新利用呢?答案就是內存緩衝繪圖。這就是咱們今天的主題。
咱們仍是回到DC上來,既然DC是繪圖對象,咱們也就能夠本身在內存裏面造一個,讓它等於咱們想要的繪圖對象,圖(CBitmap)能夠存儲在document類裏面,每一次刷新屏幕都只需將這個圖輸出到屏幕上面,每一次做圖都是在內存裏面繪製,保存在document的圖裏面,必要時還能夠將圖輸出到外存保存。這樣既保證了速度,也解決了隨機的問題,在複雜做圖的狀況下對內存的開銷也不大(老是一副圖片的大小)。這是一個很好的解決辦法,如今讓咱們來實現它們。
1. 咱們首先在document類裏面保存一個圖片
CBitmap m_bmpBuf; //這裏面保存了咱們作的圖,存在於內存中
2. 其次在view類裏面,咱們須要將這個圖拷貝到屏幕上去,於OnDraw(CDC *pDC)函數中:
CDC dcMem; // 如下是輸出位圖的標準操做
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. 在咱們須要畫圖的函數裏,完成繪圖工做
CBmpDrawDoc *pDoc = GetDocument(); // 獲得document中的bitmap對象
CDC *pDC = GetDC();
CDC dcMem;
dcMem.CreateCompatibleDC(NULL); // 這裏咱們就在內存中虛擬建造了DC
pDoc->m_bmpBuf.DeleteObject();
pDoc->m_bmpBuf.CreateCompatibleBitmap(pDC,100,100); // 依附DC建立bitmap
CBitmap *pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf); // 調入了咱們的bitmap目標
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();
所有的過程就是這樣,很簡單吧。以此爲例子還能夠實現2個緩衝或者多個緩衝等等,視具體狀況而定。固然在緩衝區還能夠實現不少高級的圖形操做,好比透明,合成等等,取決於具體的算法,須要對內存直接操做(其實就是當年dos怎麼作,如今還怎麼作)。
再來解釋一下前面說的爲何不能用全局變量保存DC問題:其實DC也是用句柄來標識的,因此也具備句柄的不肯定性,就是隻能隨用隨取,不一樣時間兩次取得的是不一樣的(使用過文件句柄地話,應該很容易理解的)。那麼咱們用全局變量保存的DC就沒什麼意義了,下次使用只是什麼也畫不出來。(這一點的理解能夠這樣:DC須要佔用必定的內存,那麼在頻繁的頁面調度中,位置不免改變,因而用來標誌指針的句柄也就不一樣了)。
*********************************************************************************************************************************************
顯示圖形如何避免閃爍
顯示圖形如何避免閃爍,如何提升顯示效率是問得比較多的問題。並且多數人認爲MFC的繪圖函數效率很低,老是想尋求其它的解決方案。MFC的繪圖效率的確不高但也不差,並且它的繪圖函數使用很是簡單,只要使用方法得當,再加上一些技巧,用MFC能夠獲得效率很高的繪圖程序。 我想就我長期(呵呵固然也只有2年多)使用MFC繪圖的經驗談談個人一些觀點。
一、顯示的圖形爲何會閃爍?
咱們的繪圖過程大多放在OnDraw或者OnPaint函數中,OnDraw在進行屏幕顯示時是由OnPaint進行調用的。當窗口因爲任何緣由須要重繪時,老是先用背景色將顯示區清除,而後才調用OnPaint,而背景色每每與繪圖內容反差很大,這樣在短期內背景色與顯示圖形的交替出現,使得顯示窗口看起來在閃。若是將背景刷設置成NULL,這樣不管怎樣重繪圖形都不會閃了。 固然,這樣作會使得窗口的顯示亂成一團,由於重繪時沒有背景色對原來繪製的圖形進行清除,而又疊加上了新的圖形。
有的人會說,閃爍是由於繪圖的速度太慢或者顯示的圖形太複雜形成的,其實這樣說並不對,繪圖的顯示速度對閃爍的影響不是根本性的。
例如在OnDraw(CDC *pDC)中這樣寫:
pDC->MoveTo(0,0);
pDC->LineTo(100,100);
這個繪圖過程應該是很是簡單、很是快了吧,可是拉動窗口變化時仍是會看見閃爍。其實從道理上講,畫圖的過程越複雜越慢閃爍應該越少,由於繪圖用的時間與用背景清除屏幕所花的時間的比例越大人對閃爍的感受會越不明顯。好比:清楚屏幕時間爲1s繪圖時間也是爲1s,這樣在10s內的連續重畫中就要閃爍5次;若是清楚屏幕時間爲1s不變,而繪圖時間爲9s,這樣10s內的連續重畫只會閃爍一次。這個也能夠試驗,在OnDraw(CDC *pDC)中這樣寫:
for(int i=0;i<100000;i++)
{
pDC->MoveTo(0,i);
pDC->LineTo(1000,i);
}
呵呵,程序有點變態,可是能說明問題。
說到這裏可能又有人要說了,爲何一個簡單圖形看起來沒有複雜圖形那麼閃呢?這是由於複雜圖形佔的面積大,重畫時形成的反差比較大,因此感受上要閃得厲害一些,可是閃爍頻率要低。那爲何動畫的重畫頻率高,而看起來卻不閃?這裏,我就要再次強調了,閃爍是什麼?閃爍就是反差,反差越大,閃爍越厲害。由於動畫的連續兩個幀之間的差別很小因此看起來不閃。若是不信,能夠在動畫的每一幀中間加一張純白的幀,不閃纔怪呢。
二、如何避免閃爍
在知道圖形顯示閃爍的緣由以後,對症下藥就好辦了。(1). 首先是去掉MFC 提供的背景繪製過程。實現的方法不少:
* 能夠在窗口造成時給窗口的註冊類的背景刷賦NULL
* 也能夠在造成之後修改背景
static CBrush brush(RGB(255,0,0));
SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);
* 要簡單也能夠重載OnEraseBkgnd(CDC* pDC)直接返回TRUE
這樣背景沒有了,結果圖形顯示的確不閃了,可是顯示也象前面所說的同樣,變得一團亂。怎麼辦?(2). 這就要用到雙緩存的方法了。雙緩衝就是除了在屏幕上有圖形進行顯示之外,在內存中也有圖形在繪製。咱們能夠把要顯示的圖形先在內存中繪製好,而後再一次性的將內存中的圖形按照一個點一個點地覆蓋到屏幕上去(這個過程很是快,由於是很是規整的內存拷貝)。這樣在內存中繪圖時,隨便用什麼反差大的背景色進行清除都不會閃,由於看不見。當貼到屏幕上時,由於內存中最終的圖形與屏幕顯示圖形差異很小(若是沒有運動,固然就沒有差異),這樣看起來就不會閃。
三、如何實現雙緩衝
首先給出實現的程序,而後再解釋,一樣是在OnDraw(CDC *pDC)中:
CRect rc; // 定義一個矩形區域變量
GetClientRect(rc);
int nWidth = rc.Width();
int nHeight = rc.Height();
CDC *pDC = GetDC(); // 定義設備上下文
CDC MemDC; // 定義一個內存顯示設備對象
CBitmap MemBitmap; // 定義一個位圖對象
//創建與屏幕顯示兼容的內存顯示設備
MemDC.CreateCompatibleDC(pDC);
//創建一個與屏幕顯示兼容的位圖,位圖的大小可選用窗口客戶區的大小
MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
//將位圖選入到內存顯示設備中,只有選入了位圖的內存顯示設備纔有地方繪圖,畫到指定的位圖上
CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
//先用背景色將位圖清除乾淨,不然是黑色。這裏用的是白色做爲背景
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
//繪圖操做等在這裏實現
MemDC.MoveTo(……);
MemDC.LineTo(……);
MemDC.Ellipse(……);
//將內存中的圖拷貝到屏幕上進行顯示
pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
//繪圖完成後的清理
MemDC.SelectObject(pOldbitmap);
MemBitmap.DeleteObject();
上面的註釋應該很詳盡了,廢話就很少說了。
四、如何提升繪圖的效率
我主要作的是電力系統的網絡圖形的CAD軟件,在一個窗口中每每要顯示成千上萬個電力元件,而每一個元件又是由點、線、圓等基本圖形構成。若是真要在一次重繪過程重畫這麼多元件,可想而知這個過程是很是漫長的。若是加上了圖形的瀏覽功能,鼠標拖動圖形滾動時須要進行大量的重繪,速度會慢得讓用戶將沒法忍受。怎麼辦?只有再研究研究MFC的繪圖過程了。
實際上,在OnDraw(CDC *pDC)中繪製的圖並非全部都顯示了的,例如:你在OnDraw中畫了兩個矩形,在一次重繪中雖然兩個矩形的繪製函數都有執行,可是頗有可能只有一個顯示了,這是由於MFC自己爲了提升重繪的效率設置了裁剪區。裁剪區的做用就是:只有在這個區內的繪圖過程纔會真正有效,在區外的是無效的,即便在區外執行了繪圖函數也是不會顯示的。由於多數狀況下窗口重繪的產生大可能是由於窗口部分被遮擋或者窗口有滾動發生,改變的區域並非整個圖形而只有一小部分,這一部分須要改變的就是pDC中的裁剪區了。由於顯示(往內存或者顯存都叫顯示)比繪圖過程的計算要費時得多,有了裁剪區後顯示的就只是應該顯示的部分,大大提升了顯示效率。可是這個裁剪區是MFC設置的,它已經爲咱們提升了顯示效率,在進行復雜圖形的繪製時如何進一步提升效率呢?那就只有去掉在裁剪區外的繪圖過程了。能夠先用pDC->GetClipBox()獲得裁剪區,而後在繪圖時判斷你的圖形是否在這個區內,若是在就畫,不在就不畫。但若是你的繪圖過程不復雜,這樣作可能對你的繪圖效率不會有提升。
*********************************************************************************************************************************************
雙緩存即如今內存dc中做圖,然後一次性地拷貝到屏幕上,因此提升了繪圖的速度。但只用此方法不能根本解決閃爍的問題。
而將響應 WM_ERASEBKGND 的重載函數 OnEraseBkgnd(CDC* pDC) 直接返回TRUE是最好的辦法。
以下:
BOOL CMyWin::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
//return CWnd::OnEraseBkgnd(pDC); //把系統原來的這條語句註釋掉
}
*********************************************************************************************************************************************
如何修改控件的背景模式及控件的字體顏色
1. 改變對話框的背景色
在C…App類中的InitInstance()裏添加
SetDialogBkColor(RGB(0,192,0),RGB(0,0,0));
2. 若是想改變靜態文本或單選按鈕的背景色,首先須要得到控件ID,而後設置背景色,具體步驟:
(1) 響應對話框類的WM_CTLCOLOR消息,生成OnCtlColor函數
(2) 爲對話框類添加成員變量CBrush m_brush;
並在初始化函數中初始化m_brush.CreateSolidBrush(RGB(0,255,0)); //顏色在這裏設置
(3) 在OnCtlColor函數中添加代碼,以改變控件的文字顏色和背景色
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的聯繫是什麼?
轉自:http://topic.csdn.net/u/20091012/14/2b948708-6d7b-498a-9806-a2adbd000c5d.html (做者:Tr0j4n)
系統重繪時,先調用OnEraseBkGnd擦除窗口的現有內容,再調用OnPaint繪製新內容。
問題就產生的:在OnEraseBkGnd中,若是你不調用原來缺省的OnEraseBkGnd只是重畫背景則不會有閃爍。而在OnPaint裏面,因爲它隱含的調用了OnEraseBkGnd,而你又沒有處理OnEraseBkGnd函數,這時就和窗口缺省的背景刷相關了。缺省的OnEraseBkGnd操做使用窗口的缺省背景刷刷新背景(通常狀況下是白刷),而隨後你又本身重畫背景形成屏幕閃動。
另外的一個問題是OnEraseBkGnd不是每次都會被調用的。若是你調用Invalidate的時候參數爲TRUE,那麼在OnPaint裏面隱含調用BeginPaint的時候就產生WM_ERASEBKGND消息,若是參數是FALSE 則不會重刷背景。
解決方法有:1. 用OnEraseBkGnd實現,不要調用原來的OnEraseBkGnd函數。
2. 用OnPaint實現,同時重載OnEraseBkGnd,並在其中直接返回TRUE。
3. 用OnPaint實現,建立窗口時設置背景刷爲空。
4. 用OnPaint實現,可是要求刷新時用Invalidate(FALSE)這樣的函數。(不過這種狀況下,窗口覆蓋等形成的刷新仍是要閃一下,因此不是完全的解決方法)
--------------------------------------------------------------------------------------------------------------------------------
在MFC中任何一個window組件的繪圖都是放在這兩個member function中。在設定上OnEraseBkgnd()是用來畫底圖的,而OnPaint()是用來畫主要對象的。
舉例說明,一個按鈕是灰色的,上面還有文字。則OnEraseBkgnd()所作的事就是把按鈕畫成灰色,而OnPaint()所作的事就是畫上文字。
既然這兩個member function都是用來畫出組件的,那爲什麼還要分OnPaint() 與 OnEraseBkgnd() 呢?
其實OnPaint() 與 OnEraseBkgnd() 特性是有差異的:1. OnEraseBkgnd()的要求是快速,在裏面的繪圖程序最好是不要太耗時間,由於每當window組件有任何小變更都會立刻呼叫OnEraseBkgnd() 。
2. OnPaint() 是隻有在程序有空閒的時候纔會被呼叫。
3. OnEraseBkgnd() 是在 OnPaint() 以前呼叫的。
因此 OnPaint() 被呼叫一次以前。可能會呼叫OnEraseBkgnd()好幾回。
若是咱們是一個在作圖形化使用者接口的人,常會須要把一張美美的圖片設爲咱們dialog的底圖。把繪圖的程序代碼放在OnPaint() 之中,可能會常碰到一些問題。比方說拖曳一個窗口在咱們作的dialog上面一直移動,則dialog會變成灰色,直到動做中止才恢復。這是由於每次須要重繪的時候,程序都會立刻呼叫OnEraseBkgnd()。而OnEraseBkgnd()就把dialog畫成灰色,只有在動做中止以後,程序纔會呼叫OnPaint(),這時纔會把咱們要畫的底圖貼上去。
這個問題的解法:1. 比較差點的方法是把OnEraseBkgnd() 改寫成不作事的function ,以下所示:
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
以上原本是會呼叫CDialog::OnEraseBkgnd() ,可是若是咱們不呼叫的話,程序便不會畫上灰色的底色了。
2. 比較好的作法是,直接將繪圖的程序從OnPaint()移到OnEraseBkgnd()來作,以下所示 :
// m_bmpBKGND 爲一CBitmap對象,且事先早已加載咱們的底圖
// 底圖的大小與咱們的窗口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;
}
特別要注意的是,取得重畫大小是使用GetUpdateRect() 而不是GetClientRect()。若是使用GetClientRect() 則會把不應重畫的地方重畫。
*****************************************************************************************************************************************
雙緩衝加劇載onpaint,OnEraseBkgnd解決屏幕閃爍問題
轉自:http://hi.baidu.com/lovevc2008/blog/item/9bc5a90b2a3eab1894ca6b0e.html
本身實現了按鈕切換背景功能後,正暗自爽的我發現了一個很嚴重的問題.背景切換時老是先出現mfc自帶的灰色難看界面才刷出我用form image控件載入的圖片.上網google了好久.總算是解決我本身的問題.
分三步走:
第一, 在OnInitDialog中寫入
//////////載入背景圖
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;
///////////////////////該斷程序用來取得加載的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; //獲得圖像高度
/////獲得了圖像的寬度和高度後,咱們就能夠對圖像大小進行適應,即調整控件的大小,讓它正好顯示一張圖片/////
CRect rect;
GetDlgItem(IDC_BAK)->GetWindowRect(&rect);
ScreenToClient(&rect);
GetDlgItem(IDC_BAK)->MoveWindow(rect.left,rect.top,cx,cy,true);//調整大小第二,重載onpaint函數
//////////////如下三種狀況任選一種會是不一樣效果(只能一種存在)///////////
//CPaintDC dc(this); //若用此句,獲得的是對話框的DC,圖片將被繪製在對話框上.
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建立兼容的內存DC : CreateCompatibleDC()
memdc.CreateCompatibleDC(&dc);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc, rcclient.Width(), rcclient.Height());// Step 2: 把位圖選入設備環境:SelectObject(),能夠理解爲選擇畫布
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 程序界面閃爍問題的一些經驗
轉載自:http://blog.joycode.com/yaodong/archive/2004/11/26/39764.joy
通常的windows 複雜的界面須要使用多層窗口並且要用貼圖來美化,因此不可避免在窗口移動或者改變大小的時候出現閃爍。
先來談談閃爍產生的緣由
緣由一:
若是熟悉顯卡原理的話,調用GDI函數向屏幕輸出的時候並非馬上就顯示在屏幕上只是寫到了顯存裏,而顯卡每隔一段時間把顯存的內容輸出到屏幕上,這就是刷新週期。通常顯卡的刷新週期是1/80秒左右,具體數字能夠本身設置的。
這樣問題就來了,通常畫圖都是先畫背景色,而後再把內容畫上去,若是這兩次操做不在同一個刷新週期內完成,那麼給人的視覺感覺就是,先看到只有背景色的圖像,而後看到畫上內容的圖像,這樣就會感受閃爍了。
解決方法:儘可能快的輸出圖像,使輸出在一個刷新週期內完成,若是輸出內容不少比較慢,那麼採用內存緩衝的方法,先把要輸出的內容在內存準備好,而後一次輸出到顯存。要知道一次API調用通常能夠在一個刷新週期內完成。
對於GDI,用建立內存DC的方法就能夠了。
緣由二:
複雜的界面有多層窗口組成,當windows在窗口改變大小的時候是先重畫父窗口,而後重畫子窗口,子父窗口重畫的過程通常沒法在一個刷新週期內完成,因此會呈現閃爍。
咱們知道父窗口上被子窗口擋住的部分其實不必重畫的。
解決方法:給窗口加個風格 WS_CLIPCHILDREN ,這樣父窗口上被子窗口擋住的部分就不會重畫了。若是同級窗口之間有重疊,那麼須要再加上 WS_CLIPSIBLINGS 風格。
緣由三:
有時須要在窗口上使用一些控件,好比IE,當你的窗口改變大小的時候IE會閃爍,即便你有了WS_CLIPCHILDREN
也沒用。緣由在於窗口的類風格有CS_HREDRAW 或者 CS_VREDRAW,這兩個風格表示窗口在寬度或者高度變化的時候重畫,可是這樣就會引發IE閃爍。解決方法:註冊窗口類的時候不要使用這兩個風格,若是窗口須要在改變大小的時候重畫,那麼能夠在WM_SIZE的時候調用RedrawWindow。
緣由四:
界面上窗口不少,並且改變大小時不少窗口都要移動和改變大小,若是使用MoveWindow或者SetWindowPos兩個API來
改變窗口的大小和位置,因爲他們是等待窗口重畫完成後才返回,因此過程很慢,這樣視覺效果就可能會閃爍。解決方法:
使用如下API來處理窗口移動,BeginDeferWindowPos, DeferWindowPos,EndDeferWindowPos。
先調用 BeginDeferWindowPos 來設定須要移動的窗口的個數,
在使用 DeferWindowPos 來移動窗口,這個API並不真的形成窗口移動,
最後用 EndDeferWindowPos 一次性完成全部窗口的大小和位置的改變。有個地方要特別注意,要仔細計算清楚要移動多少個窗口,BeginDeferWindowPos設定的個數必定要和實際的個數一致,不然在Win9x下,若是實際移動的窗口數多於調用BeginDeferWindowPos時設定的個數,可能會形成系統崩潰。在Windows NT系列下不會有這樣的問題。