几天前,我终于克服了C++窗体重绘时的闪烁问题,用到的技巧就是双缓冲。但是怎样保持住已经绘制的图形呢?也就是仿照Windows自带的画图程序一般,动态的做出一条直线。最容易想到的方法是在MouseMove过程中,不断擦除上次所画的线,然后再画出新的直线,只须增件变量保存开始的点和上次的点即可。这样做的确可以实现动态画线的功能,但是有两点不足之处。一是代码没有放在OnDraw过程中,窗体重绘时,先前所有的图形将被擦除;二是擦除上次直线的时候,难免同之前画好的线相交,将上次的线恢复为背景色的时候,很有可能也切断了之前已经做好的线。
解决这一问题的办法依然是使用双缓冲,和上次不同的上,用于缓冲的内存DC和位图要保持住,而非随用随建。为了解决第一点不足,需要建立一个内存DC和位图,并在OnDraw过程中将其拷贝到前台。这样欧文们作图的时候将图做在内存DC上,然后使窗口刷新,便看到了所做的图了。当窗体移动或被遮挡时,窗口需要重绘,只是把我们的缓冲重新绘制一遍,因此已经画好的线不会被擦除。解决第二的问题的方法主要有两种,一种是设置画笔的模式为异或模式,这样的话在同意位置画两次的话等同于什么都没画。但是这样会牺牲线条本身的颜色属性,如果背景是单一的颜色,自然是看不出来的,但是如果背景颜色丰富,那我们的线条也就随着多姿多彩了;第二种方法是比较好用的脏矩形法,脏矩形法的主要内容就是每次画面的刷新只更新需要更新的那一块区域,这正是Flash采用的方式,效率比较高。在本程序中为简单起见,没有采用完全的脏矩形法,脏矩形始终定义为整个客户区大小。为此,需要重新定义一个新的缓冲区,用来保存即将变脏的矩形,以备之后恢复所用。在响应WM_MOUSEMOVE消息的过程中,先将新缓冲区上的图形还原到旧缓冲区上,这样可以遮挡住上次的线条。再在旧缓冲区上作图,然后刷新窗口,我们便看到了动态效果。不知道这是不是传说中的三缓冲技术呢?
以下是代码的分析:
CBitmap memBakBMP; //新缓冲区内存DC CDC memBakDC; //新缓冲区用的位图 BOOL bClicked; //判断是否应当作图 CPoint ptBegin; //记录图象开始的位置 CBitmap memBMP; //新缓冲区用的位图 CDC memDC; //旧缓冲区内存DC
响应WM_CREATE消息的时候做初始化工作:
int CNSSDrawerView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; memDC.CreateCompatibleDC(NULL); memBMP.CreateCompatibleBitmap(&memDC,1024,768); memDC.SelectObject(&memBMP); memDC.FillSolidRect(0,0,1024,768,GetDC()->GetBkColor()); memBakDC.CreateCompatibleDC(NULL); memBakBMP.CreateCompatibleBitmap(&memBakDC,1024,768); memBakDC.SelectObject(&memBakBMP); return 0; }
相应的,在响应WM_DESTROY消息的时候,也要做善后工作:
void CNSSDrawerView::OnDestroy() { CView::OnDestroy(); memDC.DeleteDC(); memBMP.DeleteObject(); memBakDC.DeleteDC(); memBakBMP.DeleteObject(); }
干预WM_ERASEBKGND消息的响应,这里是最容易忽略的地方,要特别注意
BOOL CNSSDrawerView::OnEraseBkgnd(CDC* pDC) { return TRUE; //CView::OnEraseBkgnd(pDC); }
部分变量在构造函数中初始化:
CNSSDrawerView::CNSSDrawerView() { ptBegin = 0; bClicked = FALSE; }
响应WM_LBUTTONDOWN消息,着是画线的开始:
void CNSSDrawerView::OnLButtonDown(UINT nFlags, CPoint point) { ptBegin = point; bClicked = TRUE; CRect rect; GetClientRect(&rect); memBakDC.BitBlt(0,0,rect.Width(),rect.Height(),&memDC,0,0,SRCCOPY); CView::OnLButtonDown(nFlags, point); }
相应的,WM_LBUTTONUP消息的响应是画线的结束:
void CNSSDrawerView::OnLButtonUp(UINT nFlags, CPoint point) { bClicked = FALSE; CView::OnLButtonUp(nFlags, point); }
最主要的部分在响应WM_MOUSEMOVE消息的模块中:
void CNSSDrawerView::OnMouseMove(UINT nFlags, CPoint point) { if(bClicked) { CRect rect; GetClientRect(&rect); memDC.BitBlt(0,0,rect.Width(),rect.Height(),&memBakDC,0,0,SRCCOPY); memDC.MoveTo(ptBegin); memDC.LineTo(point); Invalidate(); } CView::OnMouseMove(nFlags, point); }