GDI编程

欢迎来我的个人网站:http://www.rxwcv.cn

图形设备接口(GDI)是一个可执行程序,它接受Windows应用程序的绘图请求(表现为GDI函数调用),并将它们传给相应的设备驱动程序,完成特定于硬件的输出,象打印机输出和屏幕输出。GDI负责Windows的所有图形输出,包括屏幕上输出像素、在打印机上输出硬拷贝以及绘制Windows用户界面。也就是Windows的图形编程。

一、GDI体系结构

1、  GDI32.DLL导出的函数

GDI提供几百个Windows程序中可以调用的函数。这些函数大多数是从Win32的子系统DLL GDI32.DLL中导出的。窗口管理模块UER32.DLL是使用GDI函数的大用户,它用GDI函数来绘制菜单、图标、滚动条、标题栏和每个窗口的框架等细节内容。有一些绘图函数从USER32.DLL导出,提供给应用程序。仅Windows2000 GDI32.DLL就导出了543个入口点。与DevStudio一起发行的dumpbin工具是一个简单的工具,能列出模块导出的函数。下面就是由dumpbin gdi32.dll /export 产生的部分内容:

AbortDoc
AbortPath
AddFontMemResourceEx
AddFontResourceA
AddFontResourceExA
AddFontResourceExW
AddFontResourceTracking
AddFontResourceW
AngleArc
AnimatePalette
AnyLinkedFonts
Arc
ArcTo
具体查看:http://www.cnblogs.com/del/archive/2008/03/11/1101291.html

欢迎来我的个人网站:http://www.rxwcv.cn
 
 
 

2、  GDI函数分类

GDI的功能太多了,所以我们需要一种办法对Win32 GDI API的函数分类,以便理解GDI的结构,MSDN库将GDI API分成17个领域,清楚地描述了GDI的功能。

(1)位图:处理创建、绘制设备相关位图(DDB)、设备无关位图(DIB)、DIB段、像素和区域填充的函数。

(2)画刷:处理创建、修改GDI画刷对象的函数。

(3)剪裁:处理设备上下文可绘制区域的函数。

(4)颜色:调色板管理。

(5)坐标和变换:处理映射模式、设备坐标映射逻辑和通用变换矩阵的函数。

(6)设备上下文:创建设备上下文,查询、设置其属性,及选择GDI对象的函数。

(7)填充形状:绘制闭合区域及其周线的函数。

(8)字体和文本:在系统中安装和枚举字体,并用它们绘制文本字符串的函数。

(9)直线和曲线:绘制直线、椭圆曲线和贝赛尔曲线的函数。

(10)元文件:处理Windows格式的元文件或增强型元文件的生成和回放的函数。

(11)多显示监视器:允许在一个系统中使用多个显示监视器的函数。这些函数实际上是从uer32.dll导出的。

(12)画图和绘图:负责绘图消息管理和窗口已绘图区域的函数。其中一些函数实际上是从uer32.dll导出的。

(13)路径:负责将一系列直线和曲线组成名为路径的GDI对象,并用它来绘制的函数。

(14)画笔:处理直线绘制属性的函数。

(15)打印和打印池:负责将GDI绘图命令发送到硬拷贝设备(如行式打印机和绘图仪)并平滑地管理这些任务的。打印池函数是由Win32打印池提供的,包括几个系统提供的DLL和销售自定义的模块。

(16)矩形:user32.dll提供的处理RECT结构的函数。

(17)区域:负责用区域GDI对象描述一个点集的函数,并对该点集进行操作。

还有一些没有文档记载的函数。有一些GDI函数在DDK中说明,还一些没有文档记载但系统DLL使用的函数,另处还有一些函数则没有用过。下面是这些函数的粗略分类:

用户模式打印机驱动程序。

OPENGL。

EUDC。

其他系统DLL支持。

其他没有文档记载的函数。

3、  GDI对象句柄

32位的GDI句柄由8位未知的高位、1位堆对象标记、7位对象类型信息和高4位是0的16位索引组成。借助7位对象类型信息,可以确定设备上下文、区域、位图、调色板、字体、画刷、加强型图元文件、画笔和扩展画笔。

4、GDI对象类型

GDI对象表

typedef struct

{

void * pKernel;

unsigned short nProcess;

unsigned short nCount;

unsigned short nUpper;

unsigned short nType;

void * pUser;

}GdiTableCell;

(1) pKernel指向页面池:对每个有效GDI对象,pKernel从不为空,并且值总是唯一的。因此看起来对每个GDI对象有一个相应的数据结构,这个数据结构只能从内核模式代码存取,甚至不能从GDI32.DLL直接存取。对于不同进程的对象,从pKernel的值中看不出明显区分区域来。pKernel指向的对象起始地址是0xE1000000,根据《Inside Windows NT》,起始地址是0xE1000000的区域一般是被称为“页面池”的可分页系统的内存堆。

(2)nCount 是一个部分选择计数器:在Windows 2000下,nCount总是零,就是说未使用。但在Windows NT 4.0中,某些GDI对象用了它。为了理解nCount的意义,我们试着将对象句柄选入和取消一个或多个设备上下文中,观察选入和取消是否能根据nCount值的变化而成功。

(3)nProcess使得GDI句柄绑定到进程:如果程序想使用另一个进程的GDI对象句柄,Win32 API调用一般会失败。GdiTableCell中的nProcess字段就是这种现象背后的原因。对于库存(stock)对象,如GetStockObject(BLACK_PEN),nProcess被置为零。对于用户进程创建的其他 GDI对象,nProcess是创建进程的进程标识符。

有了这个字段,GDI就会很容易地检查当前进程标识符是否和GDI对象的nProcess字段一致,目的是强制对象句柄不能在另一个进程中访问的规则。

根据微软的文档,进程终止时,由该进程创建的所有GDI对象会被释放。如果你想知道这是怎样实现的,我们现在有一点线索了。GDI只需搜索GDI对象表并删除有指定进程标识符的对象。

(4)nUpper:再次检查句柄:我们发现GDI对象表入口的nUpper字段是4字节GDI对象句柄的高两个字节的完全拷贝——对GDI对象句柄进行错误检查是低成本的冗余校验。

(5)nType:内部对象类型:nType的低字节通常和HGDIOBJ中的是7位类型信息相同,高位字节通常是零。

(6)pUser指向用户模式数据结构。

GDI对象类型列表:

5、GDI函数大致分类

   设备上下文函数(如GetDC、CreateDC、DeleteDC)、 画线函数(如LineTo、Polyline、Arc)、填充画图函数(如Ellipse、FillRect、Pie)、画图属性函数(如SetBkColor、SetBkMode、SetTextColor)、文本、字体函数(如TextOut、GetFontData)、位图函数(如SetPixel、BitBlt、StretchBlt)、坐标函数(如DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)、映射函数(如SetMapMode、SetWindowExtEx、SetViewportExtEx)、元文件函数(如PlayMetaFile、SetWinMetaFileBits)、区域函数(如FillRgn、FrameRgn、InvertRgn)、路径函数(如BeginPath、EndPath、StrokeAndFillPath)、裁剪函数(如SelectClipRgn、SelectClipPath)等。

二、三种图形输出类型   应用程序可以使用GDI创建三种类型的图形输出:矢量输出、位图图形输出和文本输出。
矢量图形输出   矢量图形输出指的是创建线条和填充图形,包括点、直线、曲线、多边形、扇形和矩形的绘制。 位图输出   位图图形的输出是指位图图形函数对以位图形式存储的数据进行操作,它包括各种位图和图标的输出。   在屏幕上表现为对若干行和列的像素的操作,在打印机上则是若干行和列的点阵的输出。   位图图形输出的优点是速度很快,它是直接从内存到显存的拷贝操作。缺点是需要额外的内存空间。 文本输出   与DOS字符方式下的输出不同,Windows是按图形方式输出的。   这样,在输出文本时,必须以逻辑坐标为单位计算文本的输出位置,而不是象DOS下以文本行为单位输出文本。这比DOS下的文本输出要难一些。   按图形方式输出文本给文本输出带来很大的灵活性。用户可以通过调用各种GDI函数,制造出各种文本输出效果,包括加粗、斜体、设置颜色等。   Windows还提供了一种TrueType(写真字体)。TrueType字体用一组直线和曲线命令及一些参数来描述字体的轮廓。Windows可以通过参数来调整直线的长度和曲线的形状,从而实现对字体的自由缩放。

三、设备描述表(DC)

在Windows环境中,各程序的输出必须限制在自己的窗口中。

GDI使用一种简单的机制保证在窗口中画图的各程序遵循这个规则。这种机制即为设备描述表(DC);当Windows程序在屏幕、打印机或其它设备上画图时,它并不是将像素直接输出到设备上,而是将图绘制到由设备描述表表示的逻辑意义上的"显示平面"上去。设备描述表是深寓于Windows中的一种数据结构,它包含GDI需要的所有关于显示平面情况的描述字段,包括相连的物理设备和各种各样的状态信息。

获取与窗口关联的设备上下文,应用程序不能使用CreateDC创建与一个窗口相关的设备上下文,但是WIN32 API提供了几个获取与窗口相关的设备上下文的函数,包括:

HDC GetWindowDC(HWND hWnd)

HDC GetDC(HWND hWnd)

HDC GetDCEx(HWND hWnd,HRGN hrgnClip,DWORD flags)

HDC BeginPaint(HWND hWnd,LPPAINTSTRUCT lpPaint)

使用GetDCEx可以代替其他的函数。例如,GetDCEx(hWnd,NULL,DCX_WIND|DCX_NORESETATTRS)可以轻松地替代GetWindowDC(hWnd),而GetDCEx(hWnd,NULL,DCX_NORESETATTRS)可以替代GetDC(hWnd)。

设备上下文类包括CDC和它的派生类CClientDC、CPaintDC、CWindowDC、CMetaFileDC。

CDC、CclientDC、CpaintDC、CwindowDC、CmetaFileDC含义与区别   CDC是设备上下文类的基类,除了一般的窗口显示外,还用于基于桌面的全屏幕绘制和非屏幕显示的打印机输出。CDC类封装了所有图形输出函数,包括矢量、位图和文本输出。   CClientDC(客户区设备上下文)用于客户区的输出,它在构造函数中封装了GetDC(),在析构函数中封装了ReleaseDC()函数。一般在响应非窗口重画消息(如键盘输入时绘制文本、鼠标绘图)绘图时要用到它。     CClientDC dc(this);//this一般指向本窗口或当前活动视图     dc.TextOut(10,10,str,str.GetLength());   CPaintDC用于响应窗口重绘消息(WM_PAINT)时的绘图输出。CPaintDC在构造函数中调用BeginPaint()取得设备上下文,在析构函数中调用EndPaint()释放设备上下文。EndPaint()除了释放设备上下文外,还负责从消息队列中清除WM_PAINT消息。因此,在处理窗口重画时,必须使用CPaintDC,否则WM_PAINT消息无法从消息队列中清除,将引起不断的窗口重画。CPaintDC也只能用在WM_PAINT消息处理之中。   CWindowDC用于窗口客户区和非客户区(包括窗口边框、标题栏、控制按钮等)的绘制。除非要自己绘制窗口边框和按钮(如一些CD播放程序等),否则一般不用它。   CMetaFileDC专门用于图元文件的绘制。图元文件记录一组GDI命令,可以通过这一组GDI命令重建图形输出。使用CMetaFileDC时,所有的图形输出命令会自动记录到一个与CMetaFileDC相关的图元文件中。

BeginPaint和GetDC区别

为什么WINDOWS要提出无效区域的概念呢?这是为了加速。因为BeginPaint和EndPaint用到的设备描述符只会在当前的无效区域内绘画,在有效区域内的绘画会自动被过滤,大家都知道,WIN GDI的绘画速度是比较慢的,所以能节省一个象素就节省一个,不用吝啬,这样可以有效加快绘画速度。        可见BeginPaint、EndPaint是比较“被动”的,只在窗口新建时和被摧残时才重画。 而GetDC用于主动绘制,只要你指到哪,它就打到哪。它不加判断就都画上去,无效区域跟它没关系。对话框没被覆盖没被摧残,它很健康,系统没要求它重画,但开发者有些情况下需要它主动重画:比如一个定时换外观的窗口,这时候就要在WM_TIMER处理代码用GetDC。这时候再用BeginPaint、EndPaint的话,会因为无效区域为空,所有绘画操作都将被过滤掉。

      由于WM_PAINT消息的优先级很低,这样,由于窗口对象不能及时收到WM_PAINT消息而影响用户对屏幕对象的视觉感觉。为弥补这个缺陷,程序员可以考虑使用函数UpdateWindows(),它在应用程序的消息队列中存在WM_PAINT消息的情况下,强使Windows立即向窗口对象发送WM_PAINT消息.

//由于WM_PAINT的优先级别很低(甚至在虚拟按键消息之后,见《windows核心编程》窗口消息一章),它只是改变了消息结构体中的QS_PAINT标志。所以呢,如果是使用函数UpdateWindows(),会直接send一个WM_PAINT消息,那样会直接调用窗口处理函数,比普通的WM_PAINT消息处理的快很多。

case WM_LBUTTONDOWN:                MessageBox(hwnd,"mouse clicked","message",0);                HDC hdc;               hdc=GetDC(hwnd);               TextOut(hdc,0,50,"博客园",strlen("博客园"));              ReleaseDC(hwnd,hdc);        break; case WM_PAINT:            HDC hDC;            PAINTSTRUCT ps;//看出区别了吗?           hDC=BeginPaint(hwnd,&ps);           TextOut(hDC,0,0,"http://www.csdn.net/",strlen("http://www.csdn.net/"));         EndPaint(hwnd,&ps);    break;

PAINTSTRUCT ps;这个结构就是为了填充无效区域的坐标等等, 这样BeginPaint就可以只画无效区域了,提高了效率。

BeginPaint可以使无效区域变有效,GetDC不改变区域属性,无效的还是无效,有效的依然是有效。 BeginPaint在WM_PAINT消息里使用,GetDC则可以在所有的消息中使用,一般是非WM_PAINT消息。

四、图形对象

图形对象类包括CGdiObject、画笔、刷子、字体、位图、调色板、区域等。   CGdiObject是图形对象类的基类,但该类不能直接为应用程序所使用。   要使用GDI对象,必须使用它的派生类:画笔、刷子、字体、位图、区域等等。   使用图形对象要注意两点:   a.同其他MFC对象一样,GDI对象的创建也要分为两步:第一步,是定义一个GDI绘图对象类的实例;第二步调用该对象的创建方法真正创建对象。   b.创建对象:使用该对象,首先要调用CDC::SelectObject()将它选入到设备上下文中,同时保存原来的设置到一个GDI对象指针比如说pOldObject中。在使用完后,再用SelectObject(pOldObject)恢复原来的设置。

1、GDI画笔和CPen类

Windows用当前选入设备描述表的画笔绘制直线和曲线,并给用Rectangle,Ellipse以及其他图形生成函数画出的图形镶画边框。默认画笔画出的是一个像素点宽的黑色实线。

如果要改变画线方式,则需创建一个GDI画笔,并由CDC::SelectOjbect将它选设备描述表,MFC用类CPen表示GDI画笔。

创建画笔的方法

创建画笔的最简单的方法是构造一个CPen对象并把定义画笔所用的参数都传送给该对象

CPen pen(PS_SOLID,1,RGB(255,0,0));

创建画笔的第二种方法是构造 一个没有初始化的CPen对象并调用CPen::CreatePen:

CPen pen;

pen.CreatePen(PS_SOLID,1,RGB(255,0,0));

创建画笔的第三种方法是构造一个没有初始化的CPen对象,向LOGPEN结构中填充描述画笔特性的参数,然后调用CPen::CreatePenIndirect生成画笔。

CPen pen;

LOGPEN lp;

lp.lognStyle = PS_SOLID;//样式

lp.lopnWidth.x = 1;//宽度

lp.lopnColor = RGB(255,0,0);//颜色

pen.CreatePenIndirect(&lp);

从以上的画笔定义可以看出,定义画笔需要三个特性:样式,宽度和颜色。画笔的样式说明请参见MSDN。

线条大约有七种风格,如下:

PS_DASH        指定一支绘制虚线的画笔

PS_DASHDOT    指定一支虚线和点交替的画笔

PS_DASHDOTDOT   指定一支虚线和两点交替的画笔

PS_DOT        指定一支点线画笔   

PS_INSIDEFRAME    指定一支带有限定形状的画笔, 线的厚度不能延伸出此形状的边界

PS_NULL        指定一支空画笔

PS_SOLID    创建一支实线画笔

2、GDI画刷和CBrush类

在默认情况下,则Rectangle,Ellipse以及其它CDC函数画出的封闭图形填充着白色像素点。通过创建GDI画刷并在画图之前将它选入设备描述表,可以改变图形的填充颜色。

MFC的CBrush类封装了GDI画刷。

画刷有三种基本类型:单色、带阴影和带图案。

单色画刷填充的单色。

阴影线画刷采用预先定义好的交叉线图案填充图形。

图案画刷用位图来填充图形。

创建单色画刷的方法类似于创建画笔,如下所示:

CBrush brush(RGB(255,0,0));

CBrush brush;

brush.CreateSolidBrush(RGB(255,0,0));

创建带阴影线画刷

CBrush brush(HS_DIAGCROSS,RGB(255,0,0));//阴影索引,COLORREF值

CBrush brush;

brush.CreateHatchBrush(HS_DIAGCROSS,RGB(255,0,0));

HS_DIAGCROSS是可供选择的六种阴影线样式之一,其它样式请参见MSDN

注意:

在用阴影线画刷填充时,除非用CDC::SetBkColor改变设备描述表的当前背景色,或用CDC::SetBkMode把背景模式OPAQUE改成TRANSPARENT,禁止背景填充,否则Windows就以默认的背景色(白色)填充阴影色线间的空白处。

3、字体与CFont

封装了GDI字体对象,用户可以建立一种GDI字体,并使用CFont的成员函数来访问它。字体包括字样(FontFamily) 、风格、尺寸三个主要属性;字体是指宋体、黑体等;风格指字体的粗细、是否倾斜等;FontStyleStrikeout是指删除线风格;可以使用GDI中的Font类,直接构造一个字体对象,如:Font font(&fontFamily, 12, FontStyleRegular, UnitPoint);为了与原来的GDI字体兼容,Font的构造函数还有另外一种形式:Font(HDC hdc, const LOGFONTW *logfont);hdc是一个窗口的设备环境句柄,logfont是指向LOGFONT(逻辑字体)数据结构的指针。

逻辑字体选到设备上下文后,可用下面几个函数来查询它所影射的物理字体的细节,及当前物理字体实例的度量信息。

int GetTextFace(HDC hDC,int nCount,LPSTR lpFaceName);

DWORD GetFontLanguageInfo(HDC hDC);

int GetTextCharSet(HDC hDC);

int GetTextCharSetInfo(HDC hDC,LPFONTSIGNATURES lpSig,DWORD dwFlags);

BOOL GetTextMetrics(HDC hDC,LPTEXTMETRIC lptm);

UINT GetOutlineTextMetrics(HDC hDC,UINT cbData,LPOUTLINETEXTMETRIC lpOTM);

4、位图

封装了GDI位图,它提供成员函数装载和操作位图。GDI支持三种位图类型:设备无关位图、设备相关位图、DIB段。

GDI绘图之位图处理函数

BitBlt            把由一个矩形中的像素组成的位块从源设备环境转移到目标设备环境中       

CreateBitmap        使用宽度、调试和颜色格式创建位图

CreateBitmapIndirect    同上,只是参数不一样

CreateCompatibleBitmap    创建与确定的设备环境关联的设备相兼容的位图

CreateDIBitmap        从一个设备无关位图中创建设备无关位图, 同时设置该位图的位

CreateDIBSection    创建应用程序能够直接写入的设备无关位图,返回一个指向该位图存入位置的指针

ExtFloodFill        用当前画刷填充显示平面的某一区域

GetBitmapDimensionEx    获取一个位图的大小

GetDIBColorTable    获取当前DIB位图颜色表中某一范围的RGB颜色值

GetDIBits        获取位图的位并使用特定格式把它们复制到一个缓冲区中

GetPixel        获取当前坐标位置处像素的RGB颜色值

GetStretchBltMode    获取当前的伸缩模式

LoadBitmap        从一个模板或可执行文件中装入确定的位图资源

MaskBlt            使用特定的掩模和光栅操作合并源位图和目的位图的颜色数据

PatBlt            使用当前被选入设备环境的画刷绘制给定的矩形

PlgBlt            把一个平行四边形中的像素颜色数据组成的位块从源设备环境转移到目标设备环境中           

SetBitmapDimensionEx    为位图指定预定的大小

SetDIBColorTable    设置当前DIB位图颜色表中某一范围的RGB颜色值

SetDIBits        使用一个特定的设备无关位图中得到的颜色数据来设置位图的像素

SetDIBitsToDevice    使用从一个特定的设备无关位图中得到的颜色数据来设置与目标环境关联的设备中特定矩形的像素

SetPixel        给特定坐标位置处的像素设置确定的颜色

SetPixelV       给特定坐标位置处的像素设置与确定颜色最近似的颜色值

SetStretchBltMode    在确定的设备环境中设置位图伸缩模式

StretchBlt        把位图从源矩形区域复制到目标矩形区域,并在需要时对位图进行拉伸或收缩

StretchDIBits        把DIB位图中矩形区域的像素颜色数据复制到确定的目标矩形中

5、调色板

封装GDI调色板,它保存着系统可用的色彩信息,是应用程序和彩色输出设备上下文的接口。

与某个设备上下文相关联的逻辑调色板可以用GetCurrentObject(hDC,OBJ_PAL)函数来获取。对于一个新的设备上下文,它的缺省调色板是由GetStockObject(DEFAULT_PALETTE)函数返回的库存缺省调色板。

6、输出文本

  GetTextMetrics(); 返回当前使用字体的尺寸描述,用于进行字体大小计算。

  SetTextColor(int nColor); 设置前景色。

  SetBkColor(int nColor); 设置背景色。

  TextOut:文字输出。

  TabbedTextOut:象TextOut一样显示正文,可以使用制表键Tab。

  ExtTextOut:在指定的矩形中显示正文。可以用该函数删去超出矩形的正文,用正文背景填充矩形,调整字符间隔。

DrawText:在指定矩形种显示正文,可以使用制表键Tab。在格式化矩形时调整正文左对齐、右对齐或居中;还可以在一个词中断开以适应矩形边界。

GDI+中的文本输出只有一个函数:DrawString DrawString(const WCHAR *string, INT length, const Font *font, const Rect &layoutRect, const StringFormat *stringFormat, const Brush *brush); DrawString(const WCHAR *string, INT length, const Font *font, const PointF &origin, const Brush *brush); DrawString(const WCHAR *string, INT length, const Font *font, const PointF &origin, const StringFormat *stringFormat, const Brush *brush); brush用来指定一个画刷,这个画刷既可以是SolidBrush和HatchBrush,也可以是TextureBrush(纹理画刷),甚至是渐变画刷。 StringFormat类来控制这些格式属性,文本的格式属性通常包括对齐方式、字符间隔以及文本调整等。

7、画点

  SetPixel:在指定坐标处按指定色彩画一点。

8、画线

  MoveToEx将直线起点移动到指定坐标处,LineTo从起点开始画直线到终点处。使用的线型由当前所用画笔指定。

9、画弧

  Arc(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4);

10、封闭图形

  矩形:Rectangle

  圆角矩形:RoundRect

  椭圆:Ellipse

  饼形图:Pie

  封闭的多边形:Polygon

11、其它绘图函数

  FillRect:用指定颜色填充矩形且不画边线。

  FloodFill:用给定的颜色,利用当前所选的刷子填充显示的底面被一个边线所包围的区域,如多边形区域的填充。

  ExtFloodFill:用给定的颜色,利用当前刷子填充表面被一个边线包围的区域,用户可以有选择地填充一个由指定颜色组成的区域。

  InvertRect:在某一矩形区域内反显现有颜色。

12、元文件

       用于记录和回放GDI函数调用。首先通过CMetaFileDC:Create来创建新的对象,如果有文件名,则保存到文件内,如果没有则建立在内存中。完毕后,用Close()关闭。 可以调用PlayMetaFile()来回放元文件。还可以用CopyMetaFile()将文件存盘。当结束元文件的时候,用DeleteMetaFile()从内存中删除。 另外有增强型元文件。 CMetaFileDC    dc; BOOL bCreate=dc.CeeateEnblanced(pDC,”metaest,wmf/0”,0,vectosd/0 this is l/0/0”); if(!bCreated) return; (具体的绘制内容……) //关闭文件,并返回文件句柄 HENHMETAFILE    hemf=dc.CloseEnhanced(); //播放文件 pDC->SaveDC(); pDC->SetMapMode(); pDC->PlayMetaFile(hemf,&rc); pDC->RestoreDC(-1); //当文件不再需要 if(hemf)     ::DeleteEnhMetaFile(hemf);

13、打印

打印相关函数

int StartDoc(HDC hDC,CONST DOCINFO *lpdi);//告诉GDI启动新打印任务

int StartPage(HDC hDC);//打印任务新页开始

int EndPage(HDC hDC);//打印任务一页结束

int EndDoc(HDC hDC);//结束StartDoc开始的打印任务

HDC ResetDC(HDC hDC,const DEVMODE *lpInitData);//改变打印设备上下文设置信息

int AbortDoc(HDC hDC);//用于非正常结束打印任务

int SetAbortProc(HDC hdc,ABORTPROC lpAbortProc);//设置回调例程,GDI周期性用它检查还是放弃打印任务

14、设备描述表属性修改/设置函数

当使用CDC输出函数在屏幕上画图时,输出的某些特性并没有在函数调用过程中规定(采用系统默认的属性画图),但我们可以通过设备描述表自身获得或得新设置属性。

MFC提供了获得和设置这些属性的函数,我们可以用这些函数方便地改变画图时的默认属性。

例如:

CDC::SetTextColor//设置文本颜色

CDC::GetTextColor//获得文本颜色

CDC::SetBkColor//设置背景颜色

CDC::SetBkMode//设置背景模式

CDC::SetMapMode//设置映射模式

CDC::CDC::SetROP2//设置绘图模式

CDC::MoveTo//当前位置

CDCL::SelectObject//当前画笔,当前画刷,当前字体

(如果想忽略背景色,可将背景设置为"Transparent",dc.SetBkMode(TRANSPARENT);)

SelectObject函数

最常用来定义设备描述表属性的CDC函数是SelectObject。

例如:

CPen pen(PS_SOLID,2,RGB(0,192,0));

CPen* pOldPen = dc->SelectObject(&pen);//把新的CDC对象选入设备描述表,同时保存旧的CDC对象

dc->Ellipse(0,0,100,100);

dc.SelectObject(pOldPen);//

绘图模式与SetROP2

GDI将像素点输出到逻辑平面上时,它不只是简单地输出像素点颜色。相反,它能过一系列的布尔运算将输出像素点的颜色和目标位置上像素点的颜色合成一起。它所使用的逻辑关系由设备描述表当前的绘图模式确定。使用CDC::SetROP2(Set Raster Operation To)可更改绘图模式。默认绘图模式为R2_COPYPEN,它将将像素点复制到显示平面上。

映射模式

默认映射模式

固定比例映射模式

可编程映射模式

简单地说,映射模式是设备描述表的属性,用于确定从逻辑坐标值到设备坐标值的转换方式。

默认的映射模式

默认的映射模式使MM_TEXT,它使以象素为单位的。X轴向左为正,Y轴向下为正。默认的坐标原点在左上角。

固定比例映射模式

固定比例的映射模式有MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS种。

它们默认的坐标原点都使在左上角。其区别在于每一个逻辑单位对应的物理大小不一样。

所对用的逻辑单位分别为0.1毫米,0.01毫米,0.01英寸,0.001英寸,1/1440英寸(0.0007英寸)。

可变比例映射模式(可编程映射模式)

对于可变比例的映射模式用户可以自己定义一个逻辑单位代表的大小,其大小可以任意。

也可以让这个大小随环境改变而改变。有MM_ISOTROPIC,MM_ANISOTROPIC这两种映射模式。

其逻辑单位的大小等于视口范围和窗口范围的比值。

两者的不同在于前者要求X轴和Y轴的度量单位必须相同,而后者没有这样的限制。

可编程映射模式

MM_ISOTROPIC和MM_ANISOTROPIC是由用户决定从逻辑坐标值转换成设备坐标值的方式,

即是由用户决定一个逻辑单位等于多少个设备单位(cm,mm,m等),而不是由Windows决定。

所以被称之为可编程映射模式;

MM_ISOTROPIC和MM_ANISOTROPIC映射模式最常用于根据窗口尺寸按比例自动调节画图的输出大小的场合。

MM_ISOTROPIC和MM_ANISOTROPIC映射模式的区别

前者中X方向和Y方向具有同一个缩放比例因子,而后者两个方向可以单独缩放;

例子:

CRect rect;

GetClientRect(&rect);

dc.SetMapMode(MM_ISOTROPIC);

dc.SetWindowExt(500,500);//设置窗口范围,将窗口的逻辑尺寸高为500单位×500单位

dc.SetViewportExt(rect.Width(),rect.Height());//设置视口范围

dc.Ellipse(0,0,500,500);

注:窗口的尺寸以逻辑单位计算,视口的尺寸以设备单位或像素计算。

特别说明

MM_TEXT为默认映射模式,其原点在窗口的左上角,X轴的正向向右,Y轴的正向向下,

并且一个逻辑单位对应于设备坐标下的一个象素

其它映射模式则原点不变,只是Y轴会翻转使正向朝上,并且逻辑单位被按比例转换为实际距离大小,而不是像素数。

需要注意的是,使用公制映射模式(非默认映射模式)时,为使输出可见,Y坐标必须为负值。

例如:

dc.Rectangle(0,0,200,100);//默认模式下画图

dc.SetMapMode(MM_LOENGLISH);//改变映射模式

dc.Rectangle(0,0,200,-100);//画图

传送给CDC输出函数的是逻辑坐标值。

设备坐标值是指窗口中相应的像素点位置。

GDI映射模式总结

http://ideage.javaeye.com/blog/294778

GDI坐标变换详细总结

http://d.download.csdn.net/down/2596722/junco007

   

编程中注意问题:

1、  设备上下文5大区域:剪裁区域、元区域,系统区域,API区域,RAO区域。对于由函数BeginPaint、GetDC或者CreateDC所返回的设备上下文来说,其剪裁区域的值是NULL,剪裁区域的值为NULL,也就意味着没有剪裁区域。没有剪裁区域意味着系统区域氛围内的所有图形都将被显示出来,而不是不显示。

2、  库存对象(StcokObject)是由操作系统预先创建的,并为所有在系统中运行的过程共享。在应用程序使用完库存对象之后,没有必要删除他们的句柄。但是调用函数DeleteObject来处理库存对象句柄是十分安全的,当函数DeleteObject没有做任何事情时,它将返回TRUE值。

3、  SetDIBitsToDevice函数与StretchDIBits函数都是显示DIB图象,二者区别是SetDIBitsToDevice函数需要的内存很少;SetDIBitsToDevice不处理拉伸,需要自己写拉伸算法;StretchDIBits可以通过SetStretchBltMode/GetStretchBltMode来控制每个设备上下文的拉伸模式。

4、在DIB和DDB间拷贝位图,GDI函数:SetDIBits/GetDIBits;

存取原始的DDB像素阵列:LONG GetBitmapBits(HBITMAP hBmp,LONG cbBuffer,LPVOID lpvBits);如何知道需要分配多大的缓冲区?在设置大小为0和缓冲区指针为NULL的情况下,GetBitmapBits函数就返回所需要的缓冲区大小。

LONG SetBitmapBits(HBITMAP hBmp,LONG cBytes,LPVOID lpBits);

DDB显示,GDI函数:BitBlt,StretchBlt

5、SetTextColor、SetBkColor函数设置新颜色之后,使用完成之后,需要再把旧颜色设置回去(通过SetTextColor、SetBkColor)。此种操作跟SelectObject类似。

字体:CreateFont、CreateFontIndirect和CreateFontIndirectEx创建GDI逻辑字体对像,将句柄返回给调用者。通过GDI对象句柄,你可以用GetObject得到定义逻辑字体的LOGFONT或ENUMLOGFONTEX结构。与其他GDI对象一样,不需要逻辑字体对象时,应用DeleteObject删除掉。

6、GDI内存泄露问题

编写GDI程序时运行多次后出现异常,除了众所周知的内存泄露以外,gdi资源泄露也是一个很直接的原因。预防gdi资源泄露措施。

1)Create出来的gdi对象,一定要用DeleteObject来释放,释放顺序是先Create的后释放,后Create的先释放. 这里的Create指的是以它为开头的gdi函数,比如,CreateDIBitmap,CreateFont等等,最后都要调用DeleteObject来释放.

2)Create出来的dc要用DeleteDC来释放,Get到的要用ReleaseDC释放.

3)确保释放DC的时候DC中的各gdi对象都不是你自己创建的;确保个gdi对象在释放的时候不被任何dc选中使用. 假如我们要使用gdi函数画图,正确的步骤应该如下: a.创建一个内存兼容dc(CreateCompatibleDC) b.创建一个内存兼容bitmap(CreateCompatibleBitmap) c.关联创建的内存兼容dc和bitmap(SelectObject) d.画图 e.BitBlt到目的dc上 f.断开内存兼容dc和bitmap关联(SelectObject) g.销毁内存兼容bitmap h.销毁内存兼容dc 由于SelectObject在选入一个新的gdi对象的时候会返回一个原来的gdi对象(假如成功的话),所以需要在步骤c的时候保存返回值,在步骤f的时候当作入口参数使用.还有,步骤g和步骤h实际上顺序可以随意,因为他们两个此刻已经没有关系了,但是为了结构清晰,我建议按照"先Create的后释放,后Create的先释放"的原则进行. 关于步骤f,可能会有争议,因为即使省略这一步,步骤g和步骤h看起来照样可以返回一个成功的值.但实际上可能并没有执行成功,至少boundschecker会报告有错,错误信息大致是说,在释放dc的时候还包含有非默认的gdi对象,在释放gdi对象的时候又说这个gdi对象还被一个dc在使用.所以,我建议保留步骤f.

4)关于98下使用CreateCompatibleBitmap 按照msdn的说法,创建出来的size不能超过16m.实际情况是这样吗?非也~!从我自己做的测试结果来看(win98se-sc),这个值在2044*2043和2044*2044之间,然而,后来在另外一个98系统上这个值也不行,后来我干脆把上限给成了2000*2000.很幸运,到现在还没有出问题,但我不能保证这个数字就是正确的.还有一点,假如宽或高有一个超过32768,哪怕另外一个值是1,也会创建失败,有兴趣的可以自己做个测试.如果要想保证这个函数在98下永远成功,可以试试下面的代码: float factor = 10.f; while(!bitmap.CreateCompatibleBitmap(&dc ,nWidth*factor ,nHeight*factor)) {    factor -= 0.01f;  } 这样至少可以保证宽和高是成比例的

5)关于在打印机上使用BitBlt 有时候在内存兼容dc里面已经做好图了,但在使用BitBlt的时候却会失败.这个时候,首先确认创建的内存兼容dc和bitmap是不是使用打印机的dc,如果确认无误,还是执行BitBlt失败,那80%可能是内存兼容bitmap太大了,请按如下方法再试试: 创建另外一个内存兼容dc2和一个比较小的内存兼容biimap2,大概是1000*1000吧,我是这样用的,然后把dc里面的内容分成块(1000*1000),把每一块BitBlt到dc2上面,再从dc2里面BitBlt到打印dc上.有人可能会有这样的疑问:那为什么不直接把dc里面的内容分几次BitBlt到打印机上呢?有区别吗?答案是肯定的,如果dc里面的bitmap太大,哪怕你想BitBlt一个10*10的区域到打印机上都会失败.

7、InvalidateRec、Invalidate、Validate、UpdateWindow

InvalidateRect使窗口的指定区域无效,Invalidate使窗口全部区域无效,Validate使窗口全部区域有效。如果窗口有区域无效,则Windows发WM_PAINT,参数中指定无效区域的最大矩形,程序中响应此消息,用BeginPaint使无效区域为剪裁区,后面的绘画活动不会超出这个剪裁区,归后由EndPaint使无效区域变为有效,这时WM_PAINT被清除。MFC为窗口类提供了WM_PAINT的消息处理。MFC为窗口类提供了WM_PAINT的消息处理函数OnPaint,OnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由OnDraw来完成。参数bErase为TRUE时,重绘区域内的背景将被擦除,否则,背景将保持不变。

UpdateWindow( )的作用是使窗口立即重绘。调用Invalidate等函数后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需要等消息队列中的其它消息发送完后才能被处理。调用UpdateWindow函数可使WM_PAINT被直接发送到目标窗口,从而导致窗口立即重绘。UpdateWindow只向窗体发送WM_PAINT消息,在发送之前判断GetUpdateRect(hWnd,NULL,TRUE)看有无可绘制的客户区域,如果没有,则不发送WM_PAINT。

Invalidate在消息队列中加入一条WM_PAINT消息,其无效区为整个客户区。

UpdateWindow直接发送一个WM_PAINT消息,其无效区范围就是消息队列中WM_PAINT消息(最多只有一条)的无效区。

效果很明显,当调用Invalidate之后,屏幕不一定马上更新,因为WM_PAINT消息不一定在队列头部,而调用UpdateWindow会使WM_PAINT消息马上执行的,绕过了消息队列。

8、双缓冲绘图

主要是建立内存兼容DC和内存兼容位图,如下:

HDC hDC = ::GetDC(m_hWnd);

hFrceDC = CreateCompatibleDC(hDC); //内存兼容DC

hFrceBmp = CreateCompatibleBitmap(hDC, WinWidth, WinHeight); //内存兼容位图

SelectObject(hFrceDC, hFrceBmp); //选入内存兼容DC

::ReleaseDC(m_hWnd, hDC);

以后所有的GDI操作,比如LineTo等,都只对hFrceDC,当要刷新的时候:

void CST_CurveCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)

{

BitBlt(pdc->m_hDC, rcInvalid.left, rcInvalid.top, rcInvalid.Width(), rcInvalid.Height(), hFrceDC, rcInvalid.left, rcInvalid.top, SRCCOPY);

...

}

这就是为什么双缓冲是GDI编程中最为基本的技巧了。这样不管你的绘制有多少的复杂,屏幕不会再有闪烁,绘制的复杂只会占用CPU多一点而已。

9、刷新背景问题

是否刷新背景很重要,有些新手将上面的双缓冲用到对话框之上,然后告诉我,还是闪烁!那是因为刷了背景的原因,因为用BitBlt绘制的东西,在windows看来,它不是一个窗口,它不像一个按钮,因为按钮是一个窗口,所以windows在刷背景的时候,会从刷新区域里面减掉按钮所占的区域,那么回到正题,在对话框上BitBlt的时候,如果位图显示占满了对话框的整个客户区,这就简单不过了,直接响应WM_ERASEBKGND消息,然后返回TRUE:

BOOL CXXX::OnEraseBkgnd(CDC* pDC)

{

return TRUE;

}

如果BitBlt不会占满整个客户区,那么很抱歉,有点麻烦了,你要生成一个刚好不包括BitBlt显示区域的区域,然后对DC执行SelectClipRgn函数,请在MSDN里面搜索“Region Functions”,里面的函数大多你可能都用得上,主要是创建区域,操作区域(比如求AND,求OR,求DIFF等)。

10、GDI函数调用次数很重要,用上双缓冲和消除刷新背景之后,闪烁的问题可以认为是解决了,下面我们将讨论减少CPU使用率的问题。GDI操作是比较浪费CPU资源的,比如频率的调用GDI函数,势必浪费CPU资源,这其中又特别是字符的打印操作,为此GDI提供PolyTextOut函数,调用它一次,可以输出任意多条的字符,每一条都是独立设置输出位置的,这个函数的使用场合我举个例子,比如你在绘制一个坐标上的刻度值,那么这个函数再好不过了。与此功能类似的还有PolylineTo、Polyline、PolyPolyline、Polygon、PolyPolygon等函数。

11、区域刷新技术,就是将客户区分为若干个区域(一般是矩形就行了,速度快),具体怎么分,分多少个没有定论,理论上,可能需要单独刷新的地方,就分为一个区域,每当这个区域需要刷新的时候,只对这个区域进行绘制,并且BitBlt到屏幕上。具体来说,每一个区域应该对应一个刷新函数(或者一个switch语句的某一个case项),这个函数执行GDI操作,当这个区域需要刷新的时候,调用这个函数将新的东西绘制到一个内存兼容DC上,然后让这个矩形失效,那么windows会在适当的时候,调用OnDraw(或类似的函数),在OnDraw里面,直接用BitBlt对无效区域(就是前面提到的那个矩形区域)进行贴图即可。

12、调用CreateCompatibleBitmap创建DDB位图。此函数创建位图时用的是系统内核的分页内存,这是稀有资源。CreateDIBSection的好处是,它使用虚拟内存创建位图。这样运行程序的实例数就只限于pagefile和磁盘空间大小了。不过CreateDIBSection比CreateCompatibleBitmap要难调用一些。

参考资料:《Windows图形编程》

一、GDI相关概念

1、GDI(Graphics Device Interface):图形设备接口,是一个应用程序与输出设备之间的中介。它提供了一套函数库,这些函数在不同的输出设备上输出图形和文字。一方面,GDI向应用程序提供一个与设备无关的编程环境,另一方面,它又以设备相关的格式和具体的设备打交道。

 

2、DC (Device Context):设备描述表(设备上下文),是一种Windows数据结构,包括了如线的宽度和颜色、刷子的样式和颜色、字体、剪裁区域等信息。用于表达显示器、打印机等设备。

DC的主要作用是进行绘图和输出文字,如绘制线条、形状和文本等,具体如dc.MoveTo(),dc.LineTo(),dc.Ellipse(),dc.FillRect(),dc.FillSolidRect(),dc.TextOut()等。

Win32下与HDC相关的函数有:GetDC(), BeginPaint()/EndPaint(),GetWindowDC()等

对应的MFC版本的类有:CDC, CPaintDC, CClientDC, CWindowDC等

 

3、GDI对象:DC定义了一组GDI对象,包括画笔,画刷,字体,位图,调色板,剪裁区域,路径层(Path)。他们有Win32和MFC两套实现版本,其对应关系如下:

 

Win32对象

MFC类

HPEN

CPen

HBRUSH

CBrush

HFONT

CFont

HBITMAP

CBitmap

HPALETTE

CPalette

HRGN

CRgn

 

4、DC与GDI对象之间的关系:GDI对象是通过DC发生作用的,要使用这些GDI对象,可以使用Win32函数SelectObject来将其选入DC中,如::SelectObject(hdc, hPen);

 

5、利用DC和GDI对象绘图的完整步骤为:

(1). 获取或者创建一个DC

(2). 获取或者创建一个GDI对象(Pen, Brush等)

(3). 使用dc.SelectObject函数把GDI对象选入DC

(4). 使用DC进行绘图或文字输出

(5). 恢复DC原来的GDI对象并删除刚新创建的GDI对象,如pen.DeleteObject()

(6). 释放或删除设备描述表DC

其中,(1)和(6),(2)和(4)是成对出现的。

 

二、设备描述表DC

Win32下获取DC的API函数有:

HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint):特定用于WM_PAINT消息

HDC GetDC(HWND hWnd):用于获得hWnd参数所指定窗口的客户区域的HDC。

HDC GetWindowDC(HWND hWnd):返回hWnd参数所指定的窗口的HDC,包括非客户区,如标题栏、菜单、滚动条,以及边框等。hWnd为NULL时,获取整个屏幕的HDC。

 

MFC对上述HDC对象和Win32函数进行了封装,基类为CDC类。CDC类包含了各种Win32 HDC的全部功能。在MFC下,使用CDC的成员函数进行图形绘制和文字输出。

CDC类有两个成员变量:m_hDC,m_hAttribDC,它们都是Windows设备描述表句柄。CDC的成员函数作输出操作时,使用m_Hdc;要获取设备描述表的属性时,使用m_hAttribDC。在创建一个CDC类实例时,缺省的m_hDC等于m_hAttribDC。

 

CDC在封装Win32函数SelectObject(HDC hdc,HGDIOBJECT    hgdiobject)时,采用了重载技术,即它针对不同的GDI对象,提供了如下名同而参数不同的成员函数:

SelectObject(CPen *pen)              //用于选入笔

SelectObject(CBitmap* pBitmap)  //用于选入位图

SelectObject(CRgn *pRgn)           //用于选入剪裁区域

SelectObject(CBrush *pBrush)      //用于选入刷子

SelectObject(CFont *pFont)         //用于选入字体

SelectPalette(CPalette *pPalette,BOOL bForceBackground )    //选入调色板到DC

RealizePalletter()                         //实现逻辑调色板到物理调色板的映射

 

直接使用CDC的例子是内存设备上下文,例如:

CDC dcMem.CreateCompatibleDC(&dc);                  //创建设备描述表

CDC pbmOld = dcMem.SelectObject(&m_bmBall);   //更改设备描述表属性

//作一些绘制操作

dcMem.SelectObject(pbmOld);            //恢复设备描述表的属性

dcMem.DeleteDC();                           //可以不调用,而让析构函数去删除设备描述表

 

从CDC 派生出四个功能更具体的DC类。继承层次如下图所示:

 

 

下面分别讨论这四种设备描述表。

l  CCientDC:代表窗口客户区的设备描述表。其构造函数CClientDC(CWnd *pWin)通过::GetDC获取指定窗口的客户区的设备描述表HDC,并且使用成员函数Attach把它和CClientDC对象捆绑在一起;其析构函数使用成员函数Detach把设备描述表句柄HDC分离出来,并调用::ReleaseDC释放设备描述表HDC。

l  CPaintDC:仅仅用于响应WM_PAINT消息时绘制窗口,因为它的构造函数调用了::BeginPaint获取设备描述表HDC,并且使用成员函数Attach把它和CPaintDC对象捆绑在一起;析构函数使用成员函数Detach把设备描述表句柄HDC分离出来,并调用::EndPaint释放设备描述表HDC,而::BeginPaint和::EndPaint仅仅在响应WM_PAINT时使用。

例如,MFC中CView对WM_PAINT消息的实现方法如下:

void CView::OnPaint()

{

CPaintDC dc(this);

OnPrepareDC(&dc);

OnDraw(&dc);

}

l  CMetaFileDC:用于生成元文件。

l  CWindowDC:代表整个窗口区(包括非客户区)的设备描述表。其构造函数CWindowDC(CWnd *pWin)通过::GetWindowDC获取指定窗口的客户区的设备描述表HDC,并使用Attach把它和CWindowDC对象捆绑在一起;其析构函数使用Detach把设备描述表HDC分离出来,调用::ReleaseDC释放设备描述表HDC。

 

三、使用DC进行绘图的基本过程

l  获取或者创建设备描述表.DC;

l  必要的话,改变设备描述表的属性(见第四节:GDI对象的介绍);

l  使用设备描述表完成绘制操作;

l  释放或删除设备描述表DC。

 

第一种绘图方式是对WM_PAINT消息的处理

void CAaView::OnPaint()

{

       CPaintDC dc(this); // 得到绘图DC

       dc.TextOut(100,100,"Hello World");   

}

或者

void CAaView::OnDraw(CDC *pDC)

{

pDC->TextOut(100,100,"Hello World");

}

上面的程序可以在窗口的100,100位置处,打印Hello World字符串。

那么什么时候会产生WM_PAINT消息呢?由于Windows是一个多任务环境,某个应用程序的窗口上面可能被对话框或窗口覆盖,当撤消这些对话框或窗口时,这个应用程序窗口中就有一个"空洞",这个"空洞"就是一块无效的用户区域。为重新显示无效用户区域,Windows发送WM_PAINT消息实现。要求Windows发送WM_PAINT的情况有:改变窗口大小,覆盖用户区的菜单或对话框关闭,使用UpdateWindow和ScrollWindow函数等。

Windows发送WM_PAINT消息时,把它放到应用程序队列的最后,使得其它的输入能够先于WM_PAINT消息被处理。GetMessage函数也得到队列中WM_PAINT消息之后的其它消息,即只有有没有其它消息的情况下,才从队列中取出WM_PAINT消息进行处理。这样做是为了让应用程序首先完成影响窗口显示结果的其它操作,不致因为频繁地执行输出操作而引起显示器的闪烁。Windows把WM_PAINT消息放在队列最后就是这个原因。

Windows并非WM_PAINT消息的唯一来源,使用InvalidateRect或InvalidateRgn函数也可以产生绘图窗口的WM_PAINT消息。这两个函数把用户区全部或部分标记成无效用户区而要求重新显示。下面的函数调用是把整个用户区标记成无效:

InvalidateRect(hWnd, NULL, TRUE);

上面代码把hWnd句柄参数指定的窗口用户区标记成无效。作为矩形结构的NULL参数指定整个用户区,TRUE参数表示擦除背景。

 

第二种绘图的方式是在非OnDraw / OnPaint中绘图

void CAaView::OnLButtonDown(UINT nFlags, CPoint point)

{

CClientDC dc(this);

dc.Ellipse(point.x-50, point.y-50, point.x+50, point.y+50);

}

这段程序实现了:以鼠标的当前位置为圆心,画一个半径为50的圆。

 

 

基本的画线函数有以下几种

CDC::MoveTo( int x, int y ); 改变当前点的位置

CDC::LineTo( int x, int y ); 画一条由当前点到参数指定点的线

CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 画弧线

CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 将多条线依次序连接

 

基本的作图函数有以下几种:

CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形

CDC::RoundRect( LPCRECT lpRect, POINT point ); 圆角矩形

CDC::Draw3dRect( int x, int y, int cx, int cy,

COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D边框

CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形

CDC::Ellipse( LPCRECT lpRect ); 椭圆形

CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );

CDC::Polygon( LPPOINT lpPoints, int nCount ); 多边形

对于矩形,圆形或类似的封闭曲线,系统会使用画笔绘制边缘,使用刷子填充内部。如果你不希望填充或是画出边缘,你可以选入空(NULL_PEN)笔或空(NULL_BRUSH)刷子。

 

多边形和剪贴区域

dc.CreateRectRgn 由矩形创建一个多边形

dc.CreateEllipticRgn 由椭圆创建一个多边形

dc.CreatePolygonRgn 创建一个有多个点围成的多边形

dc.PtInRegion 某点是否在内部

dc.CombineRgn 两个多边形相并

dc.EqualRgn 两个多边形是否相等

 

基本的绘图函数

CDC类中提供各种各样的输出操作,从画线到写字应有尽有。为了画线、矩形、圆、扇形和写字,可相应地调用一些函数。这些函数使用已选择的画笔和画刷,来画出边框,并填写图形内部区域,以及使用已选择的字体写字。

 

l  画点函数SetPixel

COLORREF CDC::SetPixel(int x,int y,COLORREF cclrref);

该函数把x和y指定的点置为clrref指定的颜色。

 

l  画线函数LineTo与移动函数MoveTo

LineTo函数用来画线,并且通常与MoveTo函数配合使用,如画一条从点(10,70)到点(250,100)的线:

dc.MoveTo(10,70);

dc.LineTo(250,100);

 

l  画矩形函数Rectangle

Rectangle函数用来画矩形。它使用已选择的画笔画出边框,使用已选择的刷子填满矩形内部。下面的例子画一个左上角位于点(10,20),右下角位于点(40,100)的矩形:

dc.Rectangle(10,20,40,100);

 

l  画圆或椭圆函数Ellipse

Ellipse函数用来画圆或椭圆。它使用已选择的笔画框,使用已选择的刷填满圆或椭圆的内部。下面的例子画一个用点(10,20)和点(40,100)构成矩形框中的椭圆:

dc.Ellipse(10,20,40,100);

 

l  画圆弧函数Arc

Arc函数用来画一段弧,这段弧由包围它的矩形和弧的开始点和结束点共同定义。下面的例子在点(10,90)和点(360,120)所指定的矩形中画一段弧,它的起点和终点分别是点(15,90)和点(360,90):

dc.Arc(10,90,360,120,15,90,360,110);

弧的起点坐标和终点坐标精确地位于弧上。

 

l  画扇形函数Pie

Pie函数用来画扇形。扇形由一段弧和两条从弧焦点到弧端点的半径组成。Pie函数使用已选择的笔画框,使已选择的刷填满扇形内部。下面的例子画一个用点(310,30)和点(360,80)构成的矩形围成的扇形。其起点和终点分别为点(360,30)和点(360,80):

dc.Pie(310,30,360,80,310,30,360,80);

弧的起点和终点不必精确地位于弧线上。

 

四、GDI对象

前面的程序只能画基本的图形,我们不能改变线条的颜色,线条的大小,不能填充颜色,也不能改变字体,显示一张位图等。要实现这些功能,我们就要使用GDI对象。不过,GDI对象是要通过DC才能发生作用的。要使用这些GDI对象,必须使用SelectObject函数将其选入DC中,如::SelectObject(hdc, hPen);

当然,使用之前,这些GDI对象必须存在,可以通过如下Win32函数来创建这些对象:

 

或者,通过Win32函数HGDIOBJ GetStockObject(int fnObject)来获取系统预先定义好的如下备用对象:

fnObject参数               含义

 

 

这里需再次强调一下:GDI对象要选入Windows 设备描述表后才能使用;用毕,要恢复设备描述表的原GDI对象,并删除该GDI对象。

一般按如下步骤使用GDI对象:

a、创建或得到一个GDI对象

b、使用dc.SelectObject函数把GDI对象选入DC

c、使用DC进行绘图或文字输出

d、恢复DC原来的GDI对象并删除刚新创建的GDI对象。

 

综合DC和GDI对象的使用步骤,则绘图的完整步骤为:

1. 获取或者创建一个DC

2. 获取或者创建一个GDI对象(Pen, Brush等)

3. 使用dc.SelectObject函数把GDI对象选入DC

4. 使用DC进行绘图或文字输出

5. 恢复DC原来的GDI对象并删除刚新创建的GDI对象,如pen.DeleteObject()

6. 释放或删除设备描述表DC

其中,1和6,2和4是对应的。

 

MFC GDI对象

MFC用一些类封装了Windows GDI对象和相关函数,层次结构如图所示:

 

 

(1)画笔

画笔决定了线条的颜色、宽度和线型(实线、点线或点划线等)。Windows使用当前在设备描述表中已选择的画笔来画线。程序中可以选择Windows的予定义画笔,也可以选择自定义的画笔。

预定义画笔有三种:BLACK_PEN(黑色笔) 、WHITE_PEN(白色笔)和NULL_PEN(空笔),这些都在windows.h中已经定义好了,程序员可使用GetStockObject函数来选择其中的一种,系统缺省的画笔为黑色笔。Windows.h包含了HPEN的数据类型定义,使用该类型可以定义画笔句柄的变量。

仅靠系统提供的预定义画笔远远不能满足需求,应用程序可根据实际需要创建一种自定义的逻辑画笔。其步骤一般为:首先用CreatePen或CreatePenIndirect函数建立一支画笔,再调用SelectObject函数将其选入设备描述表,此后就可使用该画笔在选定的设备描述表中进行绘图操作。任何时候某一设备描述表只能有一支画笔被选入作为当前画笔,当一支画笔被选入时,原先已选入的画笔便不再有效。完成绘图操作后,可以通过调用DeleteObject来释放已建立的画笔。

 

* 函数CreatePen()

语法:HPEN CreatePen(int fnPenStyle,int nWidth,COLORREF clrref);

说明:该函数创建一个逻辑画笔。其中

fnPenStyle参数指定画笔的线型,该参数可取由windows.h定义的七个标识符之一,其含义为:

PS_SOLID                   实线

PS_DASH                    虚线

PS_DOT                      点线

PS_DASHDOT             夹一点虚线

PS_DASHDOTDOT      夹二点虚线

PS_NULL                    无

PS_INSIDEFRAME      线画在所有构件框架内

nWidth参数是用逻辑单位表示的画笔的宽度;

clrref参数是一个COLORREF类型的颜色值,指定画笔的颜色,可用宏指令RGB构造这个值,如:clrref=RGB(byRed,byGreen,byBlue);

在使用CreatPen函数时,要检查其返回值,确保它是一个有效的句柄。

下面给出一段程序,说明建立、选择和释放画笔的一般方法,假定程序要用一支宽度为3的黑色作图,则程序如下:

CPen *p_Pen;

CDC dc;

p_Pen->CreatePen(PS_SOLD,3,RGB(0,0,0));

if(p_Pen)

{

dc.SelectObject(p_Pen);

//…                       //这里进行绘图操作

}

Delete p_Pen;               //删除hPen画笔,释放空间

 

* 函数CreatePenIndirect()

语法:HPEN CreatePenIndirect(LOGPEN FAR* lpLogPen);

说明:该函数用lpLogPen所指的LOGPEN结构中的信息创建一个逻辑画笔。

LOGPEN的结构如下:

typedef struct tagLOGPEN(

WORD           lopnStyle;

POINT           lopnWidth;

COLORREF   lopnColor;

) LOGPEN;

 

其中lopnStyle指定画笔线型,该参数可取下列值之一:

PS_SOLID                   0

PS_DASH                    1

PS_DOT                      2

PS_DASHDOT             3

PS_DASHDOTDOT      4

PS_NULL                    5

PS_INSIDEFRAME      6

 

nWidth参数是用逻辑单位表示的画笔的宽度

clrref参数是一个COLORREF类型的颜色值,指定画笔的颜色,可用宏指令RGB构造这个值。

 

 

(2)刷子

 

当我们在绘制一些区域图形时,其内部往往需要以某种图案进行填充,这就需要选定"刷子"作为绘图工具。Windows系统不仅为用户提供了预定义刷子,而且还允许应用程序自定义刷子。

Windows系统中预定义的刷子有如下七种:

BLACK_BRUSH          黑色刷子

DKGRAY_BRUSH       深灰色刷子

GRAY_BRUSH            灰色刷子

HOLLOW_BRUSH       中空刷子,画边界而不填充

LTGRAY_BRUSH         浅灰色刷子

NULL_BRUSH             空刷子

WHITE_BRUSH           白色刷子

应用程序可以调用GetStockObject函数选用其中一个,系统缺省的刷子是白色刷子。Window.h包含了HBRUSH数据类型的定义,使用该类型就可定义刷子句柄的变量。

 

仅靠这七种刷子往往不能满足要求,应用程序通过调用如下几种函数创建逻辑刷子,这些函数返回值均为刷子句柄。

 

* 函数CreateHatchBrush()

语法:HBRUSH CreateHatchBrush(int fnStyle, COLORREF clrref);

说明: 该创建一个带阴影的逻辑刷子。

FnStyle指定的阴影格式如下:

HS_BDLAGONAL 45度向上斜线组成的阴影图案(自左到右)

HS_CROSS                  水平和垂直交叉组成的阴影图案

HS_DIAGCROSS         45度斜线交叉组成的阴影图案

HS_FDIAGONAL         45度向下斜线组成的阴影图案(自左到右)

HS_HORZONA            水平线组成的阴影图案

HS_VERTICAL            垂直线组成的阴影图案

Clrref是具有COLORREF类型定义的刷子颜色值,可用宏指令RGB构造这个值。

 

* 函数CreateSolidBrush()

语法:HBRUSH CreateSolidBrush(COLORREF clrre);

说明:该函数创建的是一种实心颜色的逻辑刷子。clrre含义同上。

同样,使用创建刷子的函数时,要检查其返回,确保它是一个有效的句柄。

一旦创建了绘图工具之后,可以SelectObject函数把它选择到显示缓冲区里。

在使用显示缓冲区之前,并不一定非要创建和选择绘图工具,Windows为每个显示缓冲区提供默认的绘图工具。例如:黑色笔,白色刷子和系统字体。

DeleteObject函数用来删除不再需要的绘图工具,但不能删除一个已选进显示缓冲区的绘图工具,而是应该使用SelectObject函数恢复原有的绘图工具,然后再删除需要删除的工具。

 

(3) 填 充 图 形

绘制一些需要以某种图案进行填充的区域图形时,需要选定"刷子"作为绘图工具。Windows系统中预定义的刷子有七种,应用程序可以调用GetStockObject函数选用其中一个,系统缺省的刷子是白色刷子。

当靠这七种刷子不能满足要求时,应用程序通过调用Windows函数创建逻辑刷子,这些函数返回值均为刷子句柄。

 

(4) 文字与字体

Windows是使用定义好的与设备无关的字符集,Windows的"文本"字符也是图形,所以屏幕上所显示的用打印机或绘图仪等输出品的文本完全一样,做到"所见即所得"。

 

文本绘制函数有:

TextOut             以当前的字体写一字符串

DrawText           在一个特定矩形区中绘制某一格式的文本

ExtTextOut        在一个特定矩形区中,以当前字体写一字符串

GrayString         用灰色文本写一字符串

TabbedTextOut   写一带扩展字符的字符串

 

要输出文本就离不开字体。获取字体的相关信息可以使用函数:

BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics )

结构TEXTMETRIC的定义如下所示:

typedef struct tagTEXTMETRIC {       // tm

LONG tmHeight;                        //字符高度

LONG tmAscent;                        //字符上部高度(基线以上)

LONG tmDescent;                       //字符下部高度(基线以下)

LONG tmInternalLeading;            //由tmHeight定义的字符高度的顶部空间数目

LONG tmExternalLeading;           //加在两行之间的空间数目

LONG tmAveCharWidth;             //平均字符宽度

LONG tmMaxCharWidth;            //最宽字符的宽度

LONG tmWeight;                        //字体的粗细轻重程度

LONG tmOverhang;                    //加入某些拼接字体上的附加高度

LONG tmDigitizedAspectX;         //字体设计所针对的设备水平方向

LONG tmDigitizedAspectY;         //字体设计所针对的设备垂直方向

BCHAR tmFirstChar;                  //为字体定义的第一个字符

BCHAR tmLastChar;                   //为字体定义的最后一个字符

BCHAR tmDefaultChar;                     //字体中所没有字符的替代字符

BCHAR tmBreakChar;                 //用于拆字的字符

BYTE tmItalic;                           //字体为斜体时非零

BYTE tmUnderlined;                   //字体为下划线时非零

BYTE tmStruckOut;                    //字体被删去时非零

BYTE tmPitchAndFamily;           //字体间距(低4位)和族(高4位)

BYTE tmCharSet;                       //字体的字符集

} TEXTMETRIC;

 

GDI字体族和字样

GDI字体族和字样表如下表所示:

字体族           字体族常量                  字样说明

Dontcare        FF_DONTCARE           System           当不能提供字体信息或字体并不

重要时使用

Decorative      FF_DECORATIVE        Symbol          新奇字体

Modern          FF_MODERN Courer,ModernIerminal 笔画大小固定的字体,但衬线可有可无

Roman           FF_ROMAN   Roman,TimeRoman       有衬线的、笔画大小可变的罗马字体

Script             FF_SCRIPT    Script                           仿手写体

Swiss             FF_SWISS     Helvetical,System          无衬线的、笔画大小可变的字体

 

* CreateFontIndirect函数

语法: HFONT CreateFontIndirect(

              CONST LOGFONT *lplf   // pointer to logical font structure

       );

说明:参数lplf是LOGFONT结构的指针。结构中含有逻辑字体的特征信息。该函数用lplf所指的LOGFONT结构中的信息创建一种逻辑字体。LOGFONT结构的定义如下:

typedef struct tagLOGFONT {                           // lf

LONG lfHeight;                                        //字高度

LONG lfWidth;                                         //字符平均宽度

LONG lfEscapement;                                 //行与水平页角度

LONG lfOrientation;                                 //基线与水平角度

LONG lfWeight;                                       //笔划的粗细

BYTE lfItalic;                                           //非零为斜体

BYTE lfUnderline;                                    //非零为下划线

BYTE lfStrikeOut;                                    //非零为中划线

BYTE lfCharSet;                                       //指定字符集

BYTE lfOutPrecision;                                //输出精度

BYTE lfClipPrecision;                               //裁剪精度

BYTE lfQuality;                                       //输出质量

BYTE lfPitchAndFamily;                           //字体的字距和族

TCHAR lfFaceName[LF_FACESIZE];        //含字体名的字符串

} LOGFONT;

 

* 函数SetTextAlign

大多数文本函数传递的参数表都要求有一个点坐标参数以定义写文本的参考点。当前文本对齐属性规定了字符串如何相对于所传递的坐标进行写。SetTextAlign函数用以设置当前文本对齐属性。

语法: UINT SetTextAlign(

         HDC hdc,     // handle to device context

         UINT fMode   // text-alignment flag

       );

说明:该函数设置文本对齐方式。Hdc是参数描述表,fuAlign是文本对齐方式

 

* GetClientRect函数

语法: BOOL GetClientRect(

              HWND hWnd,      // handle to window

              LPRECT lpRect   // address of structure for client coordinates

       );

说明: hWnd是与用户区域相关的窗口,lpRect是指向RECT结构的指针。

 

五、位图

位图是一个二维的位数组,它与图像的图素一一对应。当现实世界的图像被扫描成位图以后,图像被分割成网格,并以图素作为取样单位。在位图中的每个图素值指明了一个单位网格内图像的平均颜色。单色位图每个图素只需要一位,灰色或彩色位图中每个图素需要多个位。

 

画位图

BitBlt函数从称为「来源」的设备内容中将一个矩形区的图素传输到称为「目的(destination)」的另一个设备内容中相同大小的矩形区。

此函数的语法如下:

BitBlt (hdcDst, xDst, yDst, cx, cy, hdcSrc, xSrc, ySrc, dwROP) ;

 

xSrc和ySrc参数指明了来源图像左上角的坐标位置。

cx和cy参数是图像的宽度和高度。

xDst和yDst参数表示了复制图像位置左上角的坐标位置。

dwROP参数是位映像操作型态,可取以下值:

BLACKNESS        输出区域为黑色

DSTINVERT         反色输出区域

MERGECOPY       在源和目的间使用AND操作

MERGEPAINT      在反色后的目的和源间使用OR操作

NOTSRCCOPY     将反色后的源拷贝到目的区

PATINVERT          源和目的间进行XOR操作

SRCAND              源和目的间进行AND操作

SRCCOPY            复制源到目的区

SRCINVERT         源和目的间进行XOR操作

SRCPAINT            源和目的间进行OR操作

WHITENESS        输出区域为白色

 

拉伸位图

在BitBlt函数中,目的图像与来源图像的尺寸是相同的,因为函数只有两个参数来说明宽度和高度。如果您想在复制时拉伸或者压缩图像尺寸,可以使用StretchBlt函数。

StretchBlt函数的语法如下:

StretchBlt (hdcDst, xDst, yDst, cxDst, cyDst, hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwROP) ;

此函数添加了两个参数。现在的函数就分别包含了目的和来源各自的宽度和高度。

 

六、文本编程实例

A. 光标插入符

       CClientDC dc(this);

       TEXTMETRIC tm;               //简介TEXTMETRIC结构

       dc.GetTextMetrics(&tm);      //取得当前DC的相关文本信息,用于确定光标的宽和高

       CreateSolidCaret(tm.tmAveCharWidth/8,tm.tmHeight);      //创建光标插入符

       ShowCaret();

SetCaretPos(point);              //需要时可利用本函数移动光标

 

B. 位图插入符

CBitmap m_bitmap;              //类成员变量

       m_bitmap.LoadBitmap(IDB_BITMAP1);

       CreateCaret(&m_bitmap);

       ShowCaret();

SetCaretPos(point); //需要时可利用本函数移动光标

 

C. 使用指定字体输出字符串

       CString sText = "毛主席语录:好好学习,天天向上";

       CFont font;

       font.CreatePointFont(200, "宋体", NULL);

       CFont *pOldFont = pDC->SelectObject(&font);  //选入新字体,保存老字体

       pDC->TextOut(10, 80, sText);                           //在指定位置显示字符串

       pDC->DrawText(sText, CRect(10, 120, 160, 200), DT_LEFT);

       pDC->SelectObject(pOldFont);                          //恢复老字体

       //比较TextOut与DrawText的差异:TextOut仅仅指定了字符串显示的起点位置,会将整个字符串全部显示出来。而DrawText限定了字符串显示的矩形框,只能显示在该矩形区域内,超出该区域的字符将不会显示出来。同时指定了对齐方式。

 

       D. 设置字体背景透明,字体颜色(前景色)

pDC->SetTextColor(RGB(255, 0, 0));   //设置字体前景色,即字体颜色

pDC->SetBkColor(RGB(255, 255, 0));  //设置字体的背景色

pDC->SetBkMode(TRANSPARENT);   //设置字体背景为透明。不要和透明刷子混淆了

pDC->TextOut(10, 80, sText);              //完成各种设置后,开始显示字符串

 

详细解读:更加详细的综合示例代码见工程“TextDraw”。

 

七、图形编程实例

 

l  在Win32程序中画线

1.  定义两个全局变量用于记录鼠标按下的(x,y)坐标。

      int nOrginX;

      int nOrginY;

2.  响应鼠标按下和鼠标抬起的消息:

在Swich中加入case WM_LBUTTONDOWN:

                      case WM_LBUTTONUP:

3.  在鼠标按下时记录鼠标按下的(x,y)坐标,查MSDN得知WM_LBUTTONDOWN

lParam的低字存放x坐标,高字存放y坐标,将其取出存入nOrginX,nOrginY。

      case WM_LBUTTONDOWN:

             nOrginX=lParam & 0x0000ffff;

             nOrginY=lParam >> 16 & 0x0000ffff;

             break;

4.  在鼠标抬起时画线:

    case WM_LBUTTONUP:

           HDC hdc;

           hdc=GetDC(hwnd);

           ::MoveToEx(hdc,nOrginX,nOrginY,NULL);

           ::LineTo(hdc,LOWORD(lParam),HIWORD(lParam) );

           ::ReleaseDC(hwnd,hdc);

 

l  在MFC程序中画线:

1.  在CxxxView(其中xxx是你的工程名字)中响应鼠标按下和鼠标抬起的消息(因为只有CxxxView中才能接收到鼠标消息):

使用ClassWizard加入WM_LBUTTONDOWN,WM_LBUTTONUP的消息响应函数OnLButtonDown, OnLButtonUp。

2.  在CxxxView中添加成员变量CPoint m_ptOrigin,用于记录鼠标按下的(x,y)坐标。

CPoint是一个用于描述点的简单的类,它有两个成员变量可以存放点的(x,y)坐标。

3.  在鼠标按下时记录该点的坐标:

m_ptOrigin =point;其中point是调用OnLButtonDown传入的鼠标按下的点的坐标。

4.  在鼠标抬起时画线:

CClientDC dc(this);

dc.MoveTo(m_ptOrigin);

dc.LineTo(point);

其中CClientDC 是一个CDC的子类,在它的构造函数中调用了GetDC,析构函数中调用了ReleaseDC,简化了用户的操作。

 

l  实现橡皮筋功能:

5.  再定义一个成员变量,用于记录鼠标抬起的点,以便擦线。

CPoint m_ptEnd;

6.  在鼠标按下时记录该点的坐标:

  m_ptOrigin=m_ptEnd=point;

7.使用ClassWizard加入WM_MOUSEMOVE的消息响应函数OnMouseMove。

在鼠标移动时判断鼠标左鍵是否按下,如果按下,就不断地擦去上一条线,画出鼠标按下点到鼠标移动的当前点之间的线。

      if(MK_LBUTTON & nFlags)

      {

             CClientDC dc(this);

             dc.SetROP2(R2_NOT);

             dc.MoveTo(m_ptOrigin);

             dc.LineTo(m_ptEnd);

             dc.MoveTo(m_ptOrigin);

             dc.LineTo(point);

             m_ptEnd=point;

      }

   其中:

if (MK_LBUTTON & nFlags)

是判断鼠标左鍵是否按下。在调用OnMouseMove时,不仅为用户传来了坐标信息,还把鼠标左鍵是否按下,Shift鍵是否按下(详细信息可查MSDN)等信息放在UINT nFlags中传入OnMouseMove,用户可以检查相应位是否为1来判断相应键是否按下。

dc.SetROP2(R2_NOT);

该句设置逆转当前屏幕颜色的绘图模式。这种模式下,在屏幕上首次画出的线的是可见的,但在同一位置再画一遍时,线就不见了。这样可以方便的实现不断画线、擦线的效果。

 

详细解读:更加详细的代码见工程“MiniCAD”。

 

 

作业:

1、完善时钟程序,使时针、分针和秒针的粗细不同,颜色不同。

2、完善MiniCAD工程,增加按下鼠标中键拖动画椭圆的功能。

3、阅读MiniNotepad工程,深入理解文本编程的具体应用。

 

 

选修内容1:如何改变客户区的背景色

方法一:

int CsdiView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

     if (CView::OnCreate(lpCreateStruct) == -1)

         return -1;

 

     // TODO:  在此添加您专用的创建代码

     HBRUSH hBrush = ::CreateSolidBrush(RGB(255, 0, 0));

     ::SetClassLong(m_hWnd, GCL_HBRBACKGROUND, (LONG)hBrush);

     return 0;

}

 

方法二:

BOOL CsdiView::OnEraseBkgnd(CDC* pDC)

{

     // TODO: 在此添加消息处理程序代码和/或调用默认值

     CRect rc;

     this->GetClientRect(&rc);

     pDC->FillSolidRect(&rc, RGB(192, 255, 0));

     return TRUE;

     ////或

     //CRect rc;

     //this->GetClientRect(&rc);

     //CBrush brush(RGB(192, 255, 0));

     //pDC->FillRect(&rc, &brush);

     //return TRUE;

     //return CView::OnEraseBkgnd(pDC);

}

 

方法三:

void CsdiView::OnDraw(CDC* pDC)

{

     CsdiDoc* pDoc = GetDocument();

     ASSERT_VALID(pDoc);

     if (!pDoc)

         return;

 

     // TODO: 在此处为本机数据添加绘制代码

     CRect rc;

     this->GetClientRect(&rc);

     pDC->FillSolidRect(&rc, RGB(192, 255, 192));

}

 

方法四:

BOOL CsdiView::PreCreateWindow(CREATESTRUCT& cs)

{

     // TODO: 在此处通过修改CREATESTRUCT cs 来修改窗口类或样式

     if (!CView::PreCreateWindow(cs)) return FALSE;

     cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW,

         ::LoadCursor(NULL, IDC_ARROW), ::CreateSolidBrush(RGB(255, 0, 0)));//红色背景

     return (cs.lpszClass != NULL);

}

 

选修内容2:利用双缓存避免屏幕闪烁

void CRussiaView::OnDraw(CDC* pDC)

{

     CRussiaDoc * pDoc = GetDocument();

     ASSERT_VALID(pDoc);

     // TODO: add draw code for native data here

 

     /////////////////////////////////////////////////////////////////////////

     //

     //如下的做法能避免绘图闪烁, 强烈推荐使用. KouConghua.

     //

     //主要思想是将以前直接画在pDC上的图,改画到一个内存DC(如dcMem)中去,

     //然后使用BitBlt函数,将dcMem这个内存中的图复制到当前屏幕即pDC中去.

     //

     //具体步骤如下, 其中a 和b 选择一步即可, 不可二者都做:

     //a. 直接在OnDraw()中增加如下语句, 以改变窗口背景为透明色:

     //   ::SetClassLong(this->m_hWnd, GCL_HBRBACKGROUND,

     //                          (LONG)(HBRUSH)::GetStockObject(NULL_BRUSH));

     //b. 在CxxxView类中增加OnEraseBkgnd()消息响应函数,

     //   将其中的代码改为: return TRUE;

     //   直接返回TRUE表示告诉系统绘图时不再绘制背景,相当于设置窗口背景为NULL刷子.

     //c. 为CxxxView类增加一个成员函数void OnDrawMem(CDC &dcMem),

     //   并将你以前写在OnDraw()中的代码,移到OnDrawMem()中去即可.

     //

     /////////////////////////////////////////////////////////////////////////

 

     //1. 改变当前View窗口的背景为空刷子

     ::SetClassLong(this->m_hWnd, GCL_HBRBACKGROUND,

(LONG)(HBRUSH)::GetStockObject(NULL_BRUSH));

 

     //2. 获取当前绘图区的宽度和高度

     CRect rcClient;

     this->GetClientRect(&rcClient);

     int nWidth = rcClient.Width();

     int nHeight= rcClient.Height();

    

     //3. 创建一个和pDC兼容的内存DC: dcMem

     CDC dcMem;   

     dcMem.CreateCompatibleDC(pDC);   //pDC换成NULL也可以,指定为显示器

    

     //创建一个位图对象, 其宽度和高度就用当前绘图区的nWidth 和nHeight

     CBitmap bmp;

     bmp.CreateCompatibleBitmap(pDC, nWidth, nHeight);

 

     //将bmp选入到dcMem中, 只有选入了位图的dcMem才有地方绘图,画到指定的bmp位图上

     CBitmap * pOldBit = dcMem.SelectObject(&bmp);

    

     //4. 先用背景色将位图清除干净,这里我用的是白色作为背景

     dcMem.FillSolidRect(0, 0, nWidth, nHeight, RGB(255, 255, 255));

    

     //5. 执行真正的绘图代码, 如dcMem.MoveTo(……); dcMem.LineTo(……); 等等

     OnDrawMem(&dcMem);

 

     //6. 将dcMem中的图拷贝到pDC上进行显示. 关键点.

     pDC->BitBlt(0, 0, nWidth, nHeight, &dcMem, 0, 0, SRCCOPY);

    

     //7. 绘图完成后的清理

     bmp.DeleteObject();

     dcMem.DeleteDC();

}

 

void CRussiaView::OnDrawMem(CDC* pDC)

{

}

 

选修内容3:抓屏并保存

 

void CbjlDlg::SavePicture(CString sSaveFileName)

{

     BeginWaitCursor();

 

     BYTE *pPicData = NULL;

 

     CDC *pDC = GetDC();

     HDC hScrDC = pDC->GetSafeHdc();

     HDC hMemDC = CreateCompatibleDC( hScrDC );

    

     INT nBitsPixel = pDC->GetDeviceCaps(BITSPIXEL);

 

     CRect rcWndRect;

     GetClientRect(&rcWndRect);

     INT nWidth = rcWndRect.right;

     INT nHeight = rcWndRect.bottom;

 

     DWORD nImageSize = nWidth * nHeight * nBitsPixel / 8;

     pPicData = new BYTE[nImageSize];

 

     BITMAPINFOHEADER BmpInfoHead;

     memset(&BmpInfoHead,0,sizeof(BmpInfoHead));

     BmpInfoHead.biSize = sizeof(BmpInfoHead);

     BmpInfoHead.biWidth = nWidth;

     BmpInfoHead.biHeight = nHeight;

     BmpInfoHead.biBitCount = nBitsPixel;

     BmpInfoHead.biCompression = BI_RGB;

     BmpInfoHead.biPlanes = 1;

     BmpInfoHead.biSizeImage = nImageSize;

 

   // 创建一个与屏幕设备描述表兼容的位图

    HBITMAP hNewBit = CreateCompatibleBitmap( hScrDC, nWidth, nHeight);

 

    // 把新位图选到内存设备描述表中

    HBITMAP hOldBitmap = (HBITMAP)SelectObject( hMemDC, hNewBit );

 

    // 把屏幕设备描述表拷贝到内存设备描述表中

     StretchBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, 0, 0, nWidth, nHeight, SRCCOPY);

    

     //取得位图数据

     GetDIBits(    hMemDC, hNewBit,

                   0, nHeight,

                   pPicData, (LPBITMAPINFO)&BmpInfoHead,

                   DIB_RGB_COLORS);

 

 

     //得到屏幕位图的句柄

    SelectObject(hMemDC, hOldBitmap);

 

     //释放

     ReleaseDC(pDC);

     DeleteDC(hMemDC);

     DeleteObject(hNewBit);

 

     if (pPicData)

     {

         DWORD nLineLen = (nWidth * nBitsPixel + 31) / 32 * 4;

         DWORD nColSize = sizeof(RGBQUAD)*((nBitsPixel <= 8) ? 1<<nBitsPixel : 0);

         DWORD nImageSize = sizeof(BITMAPINFOHEADER) + nColSize +

                   (DWORD)(UINT)nLineLen*(DWORD)(UINT)nHeight;

 

         BITMAPFILEHEADER BmpFileHead;

         BmpFileHead.bfType = MAKEWORD('B','M');

         BmpFileHead.bfSize = sizeof(BITMAPFILEHEADER) +

sizeof(BITMAPINFOHEADER) + nImageSize;

         BmpFileHead.bfReserved1 = BmpFileHead.bfReserved2 = 0;

         BmpFileHead.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

 

         BITMAPINFOHEADER BmpInfoHead;

         memset(&BmpInfoHead, 0, sizeof(BITMAPINFOHEADER));

         BmpInfoHead.biSize = sizeof(BITMAPINFOHEADER);

         BmpInfoHead.biWidth = nWidth;

         BmpInfoHead.biHeight = nHeight;

         BmpInfoHead.biBitCount = nBitsPixel;

         BmpInfoHead.biCompression = BI_RGB;

         BmpInfoHead.biPlanes = 1;

         BmpInfoHead.biSizeImage = nImageSize - sizeof(BITMAPINFOHEADER) - nColSize ;

         BmpInfoHead.biXPelsPerMeter = 0 ;

         BmpInfoHead.biYPelsPerMeter = 0 ;

         BmpInfoHead.biClrUsed = (nBitsPixel <= 8) ? 1<<nBitsPixel : 0;

         BmpInfoHead.biClrImportant = 0 ;

 

         CFile file;

         if (file.Open(sSaveFileName, CFile::modeCreate | CFile::modeWrite))

         {

              file.Write(&BmpFileHead,sizeof(BmpFileHead));

              file.Write(&BmpInfoHead,sizeof(BmpInfoHead));

              file.Write(pPicData, nImageSize);

 

              file.Close();

         }

 

         delete []pPicData;

     }

 

     EndWaitCursor();

}

 

 

选修内容4:使用路径层技术绘制复杂的图形

 

     pDC->Rectangle(50, 50, 150, 150);

//pDC->Rectangle(90, 90, 200, 200);

 

     pDC->BeginPath();

     pDC->Rectangle(50, 50, 150, 150);

     pDC->EndPath();

 

     pDC->SelectClipPath(RGN_DIFF);

     pDC->Rectangle(90, 90, 200, 200);

 

欢迎来我的个人网站:http://www.rxwcv.cn
原文地址:https://www.cnblogs.com/Hewie/p/3438240.html