您的位置:首页 > 其它

在重绘了背景的对话框上用双缓冲绘图的方式绘制picture控件时出现的问题

2015-06-19 10:34 183 查看
做了一个简单的对话框,上面有一个picture控件,为了让picture控件上的内容在窗口最小化或者拖出窗口范围之后,仍然得以保存,并且防止闪烁,采用了双缓冲绘图的方式。

重载了OnPaint函数

。。。

//也可以不重绘OnEraseBkGround

PAINTSTRUCT ps;

CDC* pDC=GetDlgItem(IDC_PIC)->BeginPaint(&ps);

//CDC* pDC=GetDlgItem(IDC_PIC)->GetDC();

if (!m_dcMem->m_hDC)

{

if(NULL!=m_dcMem)

m_dcMem->DeleteDC();

CString str;

if(FALSE==m_dcMem->CreateCompatibleDC(pDC))

{

str.Format(_T("CreateCompatibleDC错误:%d"),GetLastError());

AfxMessageBox(str);

}

if(NULL!=m_bmpBackground)

m_bmpBackground->DeleteObject();

CRect rect;

m_ctrlPic.GetClientRect(&rect);

m_bmpBackground->CreateCompatibleBitmap(pDC,m_width,m_height);

unsigned int size = m_width*m_height*8;

unsigned char *bitInfo = new unsigned char[size];

memset(bitInfo,255,size);

m_bmpBackground->SetBitmapBits(size,bitInfo);

/*HBITMAP hBitmap = (HBITMAP)::LoadImage(NULL,_T("1.bmp"),IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

if(NULL ==hBitmap)

{

str.Format(_T("LoadBitmap错误:%d"),GetLastError());

AfxMessageBox(str);

}

m_bmpBackground->Attach(hBitmap);

BITMAP bitmap;

m_bmpBackground->GetBitmap(&bitmap);

m_width = bitmap.bmWidth;

m_height = bitmap.bmHeight;*/

m_recWidth = rect.Width();

m_recHeight = rect.Height();

m_dcMem->SelectObject(m_bmpBackground);

m_dcMem->SelectObject(&m_bluePen);

}

//显示背景图片

CRect rect;

GetClientRect(rect);

CPaintDC dc(this);

dc.StretchBlt(0,0,rect.Width(),rect.Height(),m_dcMemAll,0,0,

m_recWidthAll,m_recHeightAll,SRCCOPY);

//显示picture控件

BOOL res = pDC->StretchBlt(0,0,m_recWidth,m_recHeight,m_dcMem,0,0,

m_width,m_height,SRCCOPY);

GetDlgItem(IDC_PIC)->EndPaint(&ps);

ReleaseDC(pDC);

//这种方法可以不重绘背景,即OnCreate不需重绘,OnEraseBkGround也不直接返回TRUE,但是仍需要picture控件是BeginPaint的,不知道为什么,理论上来说,BeginPaint是让整个显示区域无效的

//可能是由于/article/10070238.html 中所说的

//至此我们可以看出BeginPaint(),EndPaint()和GetDC(),ReleaseDC()的区别.前一对只能用在WM_PAINT响应函数中,并且绘制背景时不会被抹掉;

//后一对随处可用,但如果用在WM_PAINT响应函数中,那么接下来将会被WM_ERASEBKGND消息的响应函数的背景绘制给抹掉.

//但是我让OnEraseBkGround直接返回TRUE,也是不可以的,why?莫非要想让画面保存,必须要用BeginPaint?

//PAINTSTRUCT ps;

//PAINTSTRUCT ps2;

//BeginPaint(&ps2);

//CDC* pDC=GetDlgItem(IDC_PIC)->BeginPaint(&ps);

////CDC* pDC=GetDlgItem(IDC_PIC)->GetDC();

//if (!m_dcMem->m_hDC)

//{

// if(NULL!=m_dcMem)

// m_dcMem->DeleteDC();

// CString str;

// if(FALSE==m_dcMem->CreateCompatibleDC(pDC))

// {

// str.Format(_T("CreateCompatibleDC错误:%d"),GetLastError());

// AfxMessageBox(str);

// }

// if(NULL!=m_bmpBackground)

// m_bmpBackground->DeleteObject();

//

// CRect rect;

// m_ctrlPic.GetClientRect(&rect);

// m_bmpBackground->CreateCompatibleBitmap(pDC,m_width,m_height);

// unsigned int size = m_width*m_height*8;

// unsigned char *bitInfo = new unsigned char[size];

// memset(bitInfo,255,size);

// m_bmpBackground->SetBitmapBits(size,bitInfo);

//

//

// /*HBITMAP hBitmap = (HBITMAP)::LoadImage(NULL,_T("1.bmp"),IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

// if(NULL ==hBitmap)

// {

// str.Format(_T("LoadBitmap错误:%d"),GetLastError());

// AfxMessageBox(str);

// }

// m_bmpBackground->Attach(hBitmap);

// BITMAP bitmap;

// m_bmpBackground->GetBitmap(&bitmap);

// m_width = bitmap.bmWidth;

// m_height = bitmap.bmHeight;*/

//

// m_recWidth = rect.Width();

// m_recHeight = rect.Height();

// m_dcMem->SelectObject(m_bmpBackground);

// m_dcMem->SelectObject(&m_bluePen);

//}

////显示picture控件

//BOOL res = pDC->StretchBlt(0,0,m_recWidth,m_recHeight,m_dcMem,0,0,

// m_width,m_height,SRCCOPY);

//GetDlgItem(IDC_PIC)->EndPaint(&ps);

//EndPaint(&ps2);

//ReleaseDC(pDC);

其中背景图片是在OnCreate中加载的

发现如果在OnPaint中对picture控件进行重绘,必须要有CDC* pDC=GetDlgItem(IDC_PIC)->BeginPaint(&ps);

直接CDC* pDC=GetDlgItem(IDC_PIC)->GetDC();则没有效果,应该是因为后面文章中所说的 至此我们可以看出BeginPaint(),EndPaint()和GetDC(),ReleaseDC()的区别.前一对只能用在WM_PAINT响应函数中,并且绘制背景时不会被抹掉;后一对随处可用,但如果用在WM_PAINT响应函数中,那么接下来将会被WM_ERASEBKGND消息的响应函数的背景绘制给抹掉.

而如果仅仅用CDC* pDC=GetDlgItem(IDC_PIC)->GetDC();不更新对话框整个的背景,则picture控件的画面可以保存,但是由于背景不更新,此时最小化后,再最大化,对话框中的其他控件的显示就会不正常。

总结而言,如果对话框重载了背景,同时picture控件采用双缓冲绘图,则可以采用的方法为:

1.在OnCreate中载入背景图片,因为此时picture控件尚未建立,所以不可在其中对picture控件的兼容DC和兼容位图进行初始化,

2.在OnPaint中对picture控件的兼容DC和兼容位图进行初始化,注意应该采用GetDlgItem(IDC_PIC)->BeginPaint(&ps),之后的

pDC->StretchBlt(0,0,m_recWidth,m_recHeight,m_dcMem,0,0, m_width,m_height,SRCCOPY);才会有效,不被背景的重绘所覆盖

3.重绘背景,重绘picture控件


BeginPaint和GetDC有什么区别?(转)


文章来源于/article/10070238.html

这是个windows编程问题。

第一种情况显示出来的字很正常。

case WM_PAINT:

gdc = BeginPaint (hwnd, &ps);

TextOut (gdc, 0, 0, s, strlen (s));

EndPaint (hwnd, &ps);

break;

第二种情况显示的字不停闪烁。

case WM_PAINT:

gdc = GetDC (hwnd);

TextOut (gdc, 0, 0, s, strlen (s));

ReleaseDC (hwnd, gdc);

break;

请教两种函数的作用?
BeginPaint() 和EndPaint() 可以删除消息队列中的WM_PAINT消息,并使无效区域有效。

GetDC()和ReleaseDC()并不删除也不能使无效区域有效,因此当程序跳出 WM_PAINT 时 ,无效区域仍然存在。系统就回不断发送WM_PAINT消息,于是程序不断处理WM_PAINT消息。
BeginPaint、EndPaint会告诉GDI内部,这个窗口需要重画的地方已经重画了,这样WM_PAINT处理完返回给系统后,系统不会再重发WM_PAINT,而GetDC没有告诉系统这个窗口需要重画的地方已经画过,在你把程序返回给系统后,系统一直以为通知你的重画命令你还没有乖乖的执行或者执行出错,所以在消息空闲时,它还会不断地发WM_PAINT催促你画,导致程序卡死。
无效区域 :
无效区域就是指需要重画的区域,无效的意思是:当前内容是旧的,过时的。

假设A是新弹出的一个对话框或被激活的现有对话框,A对话框置于原来的活动对话框B前面,造成对话框B的部分或全部被覆盖,当对话框A移开或关闭后,使对话框B原来被覆盖的地方重新可见。那部分被覆盖的地方就称为无效区域。

只有当一个窗口消息空闲时,系统才会抽空检查一下这个窗口的无效区域是否为非空(WM_PAINT的优先级是最低的。这也就是为什么系统很忙时窗口和桌面往往会出现变白、刷新不了、留拖拽痕迹等现象的原因),如果非空,系统就发送WM_PAINT。所以一定要用BeginPaint、EndPaint把无效区域设为空,否则WM_PAINT将一直被发送。
为什么WINDOWS要提出无效区域的概念?
这是为了加速。

因为BeginPaint和EndPaint用到的设备描述符只会在当前的无效区域内绘画,在有效区域内的绘画会自动被过滤,大家都知道,WIN GDI的绘画速度是比较慢的,所以能节省一个象素就节省一个,不用吝啬,这样可以有效加快绘画速度。

可见BeginPaint、EndPaint是比较“被动”的,只在窗口新建时和被摧残时才重画。

而GetDC用于主动绘制,只要你指到哪,它就打到哪。它不加判断就都画上去,无效区域跟它没关系。对话框没被覆盖没被摧残,它很健康,系统没要求它重画,但开发者有些情况下需要它主动重画:比如一个定时换外观的窗口,这时候就要在WM_TIMER处理代码用GetDC。这时候再用BeginPaint、EndPaint的话,会因为无效区域为空,所有绘画操作都将被过滤掉。
eg:
PAINTSTRUCT ps;

HDC hdc = BeginPaint(hWnd,&ps);

//Create a DC that matches the device

HDC hdcMem = CreateCompatibleDC(hdc);

//Load the bitmap

HANDLE hBmp= LoadImage(g_hInst_MainWnd,MAKEINTRESOURCE(IDB_MAINWND),IMAGE_BITMAP,0,0,0);

//Select the bitmap into to the compatible device context

HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);

//Get the bitmap dimensions from the bitmap

BITMAP bmp;

GetObject(hBmp,sizeof(BITMAP),&bmp);

//Get the window area

RECT rc;

GetClientRect(hWnd,&rc);

//Copy the bitmap image from the memory DC to the screen DC

BitBlt(hdc,rc.left,rc.top,bmp.bmWidth,bmp.bmHeight,hdcMem,0,0,SRCCOPY);

//Restore original bitmap selection and destroy the memory DC

SelectObject(hdcMem,hOldSel);

DeleteDC(hdcMem);

EndPaint(hWnd,&ps);

return 0;
/////////////////////////
下面是更加详细的介绍
//========================================================================

//TITLE:

// EVC绘制位图--BeginPaint()与GetDC()的区别

//AUTHOR:

// norains

//DATE:

// Tuesday 29-August-2006

//========================================================================

1.BeginPaint()和GetDC()

在EVC中绘制位图比较方便,有不少现成的函数可供调用,我们所要注意的只是BeginPaint()或GetDC()的使用即可.

因为代码比较简单,所以不做更多解释.

这是消息循环函数:

LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)

{

......

switch(wMsg)

{

case WM_PAINT:

OnPaintMainWnd(hWnd,wMsg,wParam,lParam);

break;

......

}

return DefWindowProc(hWnd,wMsg,wParam,lParam);

......

}

响应WM_PAINT消息的函数,在这里进行位图的绘制:

LRESULT OnPaintMainWnd(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hWnd,&ps);

//Create a DC that matches the device

HDC hdcMem = CreateCompatibleDC(hdc);

//Load the bitmap

HANDLE hBmp= LoadImage(g_hInst_MainWnd,MAKEINTRESOURCE(IDB_MAINWND),IMAGE_BITMAP,0,0,0);

//Select the bitmap into to the compatible device context

HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);

//Get the bitmap dimensions from the bitmap

BITMAP bmp;

GetObject(hBmp,sizeof(BITMAP),&bmp);

//Get the window area

RECT rc;

GetClientRect(hWnd,&rc);

//Copy the bitmap image from the memory DC to the screen DC

BitBlt(hdc,rc.left,rc.top,bmp.bmWidth,bmp.bmHeight,hdcMem,0,0,SRCCOPY);

//Restore original bitmap selection and destroy the memory DC

SelectObject(hdcMem,hOldSel);

DeleteDC(hdcMem);

EndPaint(hWnd,&ps);

return 0;

}

我们都知道BeginPaint()和EndPaint()需要配套使用,并且这两个函数也只能用在WM_PAINT消息的相应函数当中.如果我们在WM_PAINT的响应函数中将以上两个绘制函数相应替换为GetDC()和ReleaseDC()会有什么结果呢?

即:

HDC hdc = BeginPaint(hWnd,&ps); --> HDC hdc = GetDC(hWnd);

EndPaint(hWnd,&ps); --> ReleaseDC(hWnd,hdc);

编译并运行程序,我们发现窗口一片空白,好像没有绘制位图.但其实不尽然,我们采用单步调试,可以发现其实位图已经绘制出来,只不过又被背景颜色抹掉了.由此可知,如果需要使用GetDC(),我们对消息循环函数必须要加上对WM_ERASEBKGND的处理:

LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)

{

switch(wMsg)

{

case WM_PAINT:

OnPaintMainWnd(hWnd,wMsg,wParam,lParam);

break;

case WM_ERASEBKGND

return 0;

}

return DefWindowProc(hWnd,wMsg,wParam,lParam);

}

只要系统不对WM_ERASEBKGND进行默认处理,我们用GetDC()替代BeginPaint()就可以正常使用.

至此我们可以看出BeginPaint(),EndPaint()和GetDC(),ReleaseDC()的区别.前一对只能用在WM_PAINT响应函数中,并且绘制背景时不会被抹掉;后一对随处可用,但如果用在WM_PAINT响应函数中,那么接下来将会被WM_ERASEBKGND消息的响应函数的背景绘制给抹掉.

2.绘图闪烁问题

有时候我们大量绘制屏幕时,可能会出现屏幕闪烁问题,这时候可以采用双缓冲的做法.步骤首先是创建一个内存DC,然后往内存DC中绘图,最后把内存DC的内容复制到显示DC中,完成绘制.具体过程并不复杂,结合代码来说明一下.

PS:这段代码也是相应WM_PAINT 消息的.

PAINTSTRUCT ps;

HDC hdc;

//获取屏幕显示DC

hdc = BeginPaint (hWnd, &ps);

//创建内存DC

HDC hdcMem = CreateCompatibleDC(hdc);

//创建一个bmp内存空间

HBITMAP hBmp = CreateCompatibleBitmap(hdc,SCREEN_WIDTH,SCREEN_HEIGHT);

//将bmp内存空间分配给内存DC

HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);

//这是使用者需要绘制的画面,全部往内存DC绘制

Rectangle(hdcMem,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);

DrawMenuButton(hdcMem);

//将内存DC的内容复制到屏幕显示DC中,完成显示

BitBlt(hdc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,hdcMem,0,0,SRCCOPY);

//清除资源

SelectObject(hdcMem,hOldSel);

DeleteDC(hdcMem);

EndPaint(hWnd,&ps);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: