DrawCli代码中使用了双缓冲和裁剪区技术以及坐标变换等技术。
DrawCli中2个主要绘图相关的函数: OnDraw和OnPrepareDC。
OnPreparDC的作用是设置坐标的映射方式和窗口原点。由于程序使用了MM_ANISOTROPIC 映射方式,同时设置了X的正方向向右,Y轴的正方向向上。
为了便于分析理解,我们可以改造一下代码,设置程序使用MM_TEXT映射模式。
1.程序代码
新建一个新的工程,视图从CScrollView派生。
对OnPrePareDc函数中添加下面代码,设置映射模式为MM_TEXT模式
pDC->SetMapMode(MM_TEXT);
pDC->SetWindowOrg(0,0);
为了便于理解和分析,我们在OnDraw函数中绘制一个矩形。
在OnDraw函数中添加下面的代码。
CDCdc;
CDC*pDrawDC = pDC;
CBitmap bitmap;
CBitmap* pOldBitmap;
CRectclient;
pDC->GetClipBox(client);
CRectrect = client;
DocToClient(rect);
if(dc.CreateCompatibleDC(pDC))
{
if(bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()))
{
OnPrepareDC(&dc, NULL);
pDrawDC = &dc;
// offset origin more because bitmap is justpiece of the whole drawing
CPointptOrg = dc.GetViewportOrg();
dc.OffsetViewportOrg(-rect.left, -rect.top);
ptOrg= dc.GetViewportOrg();
pOldBitmap = dc.SelectObject(&bitmap);
dc.SetBrushOrg(rect.left % 8, rect.top % 8);
// might as well clip to the same rectangle
dc.IntersectClipRect(client);
}
}
// paint background
CBrushbrush;
if(!brush.CreateSolidBrush(RGB(0,255,0)))
return;
brush.UnrealizeObject();
pDrawDC->FillRect(client, &brush);
CBrushbrush1;
if(!brush1.CreateSolidBrush(RGB(255,0,0)))
return;
CPenpen;
if(!pen.CreatePen(PS_SOLID,1,RGB(0,0,0)))
return;
CBrush*pOldBrush;
CPen*pOldPen;
pOldBrush = pDrawDC->SelectObject(&brush1);
pOldPen = pDrawDC->SelectObject(&pen);
CRectrect1(0,0,100,100);
dc.Rectangle(rect1);
if(pDrawDC != pDC){
pDC->SetViewportOrg(0, 0);
pDC->SetWindowOrg(0,0);
pDC->SetMapMode(MM_TEXT);
dc.SetViewportOrg(0, 0);
dc.SetWindowOrg(0,0);
dc.SetMapMode(MM_TEXT);
pDC->BitBlt(rect.left,rect.top, rect.Width(),rect.Height(),
&dc,0, 0, SRCCOPY); // 把dc的位图拷贝到pDC环境中
dc.SelectObject(pOldBitmap);
dc.SelectObject(pOldBrush);
dc.SelectObject(pOldPen);
}
2.双缓冲技术
我们首先讲一下双缓冲技术,在上面的代码中,所谓双缓冲就是在VC中进行绘图过程处理时,如果图形刷新很快,经常出现图形闪烁的现象。利用先在内存绘制,然后拷贝到屏幕的办法可以消除屏幕闪烁,具体的方法是先在内存中创建一个与设备兼容的内存设备上下文,也就是开辟一快内存区来作为显示区域,然后在这个内存区进行绘制图形。在绘制完成后利用BitBlt函数把内存的图形直接拷贝到屏幕上即可。
在上面的代码中,dc.CreateCompatibleDC(pDC)和bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height())意思就是创建一个内存兼容DC,创建一个内存兼容位图。然后用pOldBitmap = dc.SelectObject(&bitmap)函数把将位图选入到内存显示设备中,只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上 。
3.裁剪区
只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。
上面说了这么多,用一个具体的图来加深理解。
当开始显示视图时如下所示:
![](http://hi.csdn.net/attachment/201202/5/0_1328412911ly13.gif)
通过GetClipBox函数获取的窗口大小为:
rect.l = 0,rect.t= 0,rect.r =934,rect.b=434;
当我们滑动上下的滑块时,如下图所示:
上面的黑色矩形就是裁剪区,其大小为:
rect.l = 0,rect.t= 431,rect.r =934,rect.b=434
在程序中使用用pDC->GetClipBox()得到裁剪区,然后在这个区域内进行绘图。这样做,主要是为了提高绘图效率,避免闪烁。
4.坐标变换
为了便于理解分析,我们已经把DrawCLi程序的原来的映射方式改为MM_TEXT,对于坐标变换,网上有很所资料可以参考,这里不详细讲解,这里只是讲一些主要概念。
1) SetWindowOrg(x, y) 是把设备坐标的原点(视口)映射到逻辑坐标的(X,Y)处
2) SetViewportOrg(x, y) 是把逻辑坐标的原点(窗口)映射到设备坐标的(X,Y)处
3) 设备原点永远是客户区的左上角顶点(upperleft corner of the client area)
4) 设备坐标的X, Y轴方向是固定的,单位也是固定的,X轴向右递增,Y向下递增,单位都是像素
有一个比较好的软件是viewport软件,对加深坐标变换的理解很有好处。
1) SetWindowOrg(x, y) 是把设备坐标的原点(视口)映射到逻辑坐标的(X, Y)处
![](http://hi.csdn.net/attachment/201202/5/0_1328413308O76w.gif)
void CTestViewView::OnDraw(CDC* pDC)
{
CTestViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->SetMapMode(MM_TEXT);
pDC->SetWindowOrg(100, 100);
pDC->Rectangle(0, 0, 200, 200);
}
![](http://hi.csdn.net/attachment/201202/5/0_1328413375A9Zh.gif)
2) SetViewportOrg(x, y) 是把逻辑坐标的原点(窗口)映射到设备坐标的(X, Y)处
![](http://hi.csdn.net/attachment/201202/5/0_1328413534xeXR.gif)
void CTestViewView::OnDraw(CDC* pDC)
{
CTestViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->SetMapMode(MM_TEXT);
pDC->SetViewportOrg(100, 100);
pDC->SelectStockObject(GRAY_BRUSH);
pDC->Rectangle(0, 0, 200, 200);
}
![](http://hi.csdn.net/attachment/201202/5/0_1328413609lL71.gif)
上面是对坐标变换的直观的理解和介绍。具体到DrawCli程序,主要是理解
dc.OffsetViewportOrg(-rect.left, -rect.top);
pDC->SetViewportOrg(0, 0);
pDC->SetWindowOrg(0,0);
pDC->SetMapMode(MM_TEXT);
dc.SetViewportOrg(0,0);
dc.SetWindowOrg(0,0);
dc.SetMapMode(MM_TEXT);
pDC->BitBlt(rect.left, rect.top, rect.Width(),rect.Height(),
&dc, 0, 0, SRCCOPY);
这三段代码的作用。
我们先分析逻辑坐标的情况:
开始视图显示时,视图在内存DC上的逻辑坐标的坐标为(0,0) (934,434)。
移动滑块后,clip的区域的坐标为(0,434) (934,437) 。clip的区域在屏幕客户区的坐标为:(0,431) (934,434)。
pDrawDC->FillRect(client, &brush)函数在这个客户区坐标上填充背景色,才不会有黑块出现。
dc.OffsetViewportOrg(-rect.left,-rect.top)函数把内存的DC逻辑坐标的原点移动到内存设备坐标的(0,-434)处,clip区域在设备坐标中的坐标为(0,0) (0,3)。
dc.SetViewportOrg(0, 0);
dc.SetWindowOrg(0,0);
dc.SetMapMode(MM_TEXT);
操作后,内存DC的逻辑坐标原点与设备坐标原点重合。
pDC->BitBlt(rect.left, rect.top, rect.Width(),rect.Height(),&dc,0, 0, SRCCOPY);
操作之后,复制的目标区域为(0,431) (934,434),源区域为(0,0)(0,3)。
BitBlt中的源区域的坐标是参考源DC的逻辑坐标系。