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系列下不会有这样的问题。