1、课 题: 第六章 Windows 的图形设备接口及 windows 绘图 目的要求: windows 图形输出的原理 掌握设备环境类的概念与使用 掌握基本绘图工具的使用 教学重点: 图形输出原理,绘图工具的使用 教学难点: 图形输出原理 教学课时: 4 教学方法:讲练结合 教学内容与步骤: 一、Windows 绘图过程与设备无关性图形设备接口 GDI:管理 Windows 应用程序图形的绘制,在应用程序中,通过调用GDI 函数绘制不同尺寸、颜色、风格的几何图形、文本和位图。这些图形处理函数组成了图形设备接口 GDI。是形成 Windows 核心的三种动态链接库之一:user.dll, wind
2、ows.dll,设备环境 DC 也称设备描述表或设备上下文,设备环境中的“设备”是指任何类型的显示器或打印机等输出设备。从根本上来说,设备环境 DC 是一个由 Windows 管理的一个数据结构,它保存了绘图操作中一些共同需要设置的信息,如当前的画笔、画刷、字体和位图等图形对象及其属性,以及颜色和背景等影响图形输出的绘图模式。并实现应用程序、设备驱动程序和输出设备之间绘图命令的转换。形象地说,一个设备环境提供了一张画布和一些绘画的工具,我们可以使用不同颜色的工具在上面绘制点、线、圆和文本。绘图过程:图形设备接口 GDI 处于设备驱动程序的上一层,当程序调用绘图函数时,GDI 将绘图命令传送给当
3、前设备的驱动程序,以调用驱动程序提供的接口函数。驱动程序的接口函数将 Windows 绘图命令转化为设备能够执行的输出命令,实现图形的绘制。不同设备具有不同的驱动程序,设备驱动程序是设备相关的。设备无关性:是指操作系统屏蔽了硬件设备的差异,使用户编程时一般无需考虑设备的类型,如不同种类的显示器或打印机。 简单而言:GDI 绘图语句的设备无关性。MFC 与 SDK 编程:MFC 将 GDI 函数和绘图对象封装在一个名为 CDC 的设备环境类中,因此我们可以通过调用 CDC 类的成员函数来完成绘图操作. SDK 编程:通过设备环境调用系统提供的 GDI 函数和绘图函数来完成绘图过程。获取设备环境
4、DC 的方法:两种编程方式采用 SDK 方法编程,获取 DC 的方法有两种:在 WM_PAINT 消息处理函数中通过调用 API函数 BeginPaint()获取设备环境在其他函数中通过调用 API 函数 GetDC(hwnd)获取设备环境,分别用 EndPaint(HWND hwnd, PAINTSTRUCT 2,CClientDC pdc(this); 3,CPaintDC pdc(this)CDC 类既作为其它 MFC 设备环境类的基类,又可以作为一个一般的设备环境类使用。利用它可以访问设备属性和设置绘图属性。CDC 类对 GDI 的所有绘图函数进行了封装。CPaintDC 类是 OnP
5、aint()函数使用的设备环境类,它代表一个窗口的绘图画面。如果添加 WM_PAINT 消息处理函数 OnPaint(),就需要使用 CPaintDC 类来定义一个设备环境对象。CClientDC 类代表了客户区设备环境。当在客户区实时绘图时,需要利用 CClientDC 类定义一个客户区设备环境。CWindowDC 类代表了整个程序窗口设备环境,可以在整个窗口区域绘图。 二,GDI 坐标系和映射模式: Windows 坐标系分为逻辑坐标系和设备坐标系两种,GDI 支持这两种坐标系。一般而言,GDI 的文本和图形输出函数使用逻辑坐标,而在客户区移动或按下鼠标的鼠标位置是采用设备坐标。 逻辑坐标
6、系是面向 DC 的坐标系,这种坐标不考虑具体的设备类型,在绘图时,Windows 会根据当前设置的映射模式将逻辑坐标转换为设备坐标。设备坐标系是面向物理设备的坐标系,这种坐标以像素或设备所能表示的最小长度单位为单位,X 轴方向向右,Y 轴方向向下。设备坐标系的原点位置(0, 0)不限定在设备显示区域的左上角。设备坐标系分为屏幕坐标系、窗口坐标系和客户区坐标系三种相互独立的坐标系。 屏幕坐标系以屏幕左上角为原点,一些与整个屏幕有关的函数均采用屏幕坐标,如GetCursorPos()、SetCursorPos()、CreateWindow()、MoveWindow()。弹出式菜单使用的也是屏幕坐标
7、。 窗口坐标系以窗口左上角为坐标原点,它包括窗口标题栏、菜单栏和工具栏等范围。 客户区坐标系以窗口客户区左上角为原点,主要用于客户区的绘图输出和窗口消息的处理。鼠标消息的坐标参数使用客户区坐标,CDC 类绘图成员函数使用与客户区坐标对应的逻辑坐标。坐标之间的相互转换: MFC 提供了两个函数 CWnd:ScreenToClient()和 CWnd:ClientToScreen()用于屏幕坐标与客户区坐标的相互转换。 MFC 提供了两个函数 CDC:DPtoLP()和 CDC: LPtoDP()用于设备坐标与逻辑坐标之间的相互转换。 映像模式(设备环境类对象调用其成员设置窗口映像模式)设置窗口映
8、像模式: 映像模式定义了将逻辑单位转化为设备的度量单位以及设备的 x 方向和 y 方向,程序员可在一个统一的逻辑坐标系中操作而不必考虑输出设备的坐标系情况映射模式 逻辑单位 坐标系设定MM_TEXT 一个像素 X轴正方向朝右,Y轴正方向朝下MM_LOMETRIC 0.1毫米 X轴正方向朝右,Y轴正方向朝上MM_HIMETRIC 0.01毫米 X轴正方向朝右,Y轴正方向朝上MM_LOENGLISH 0.01英寸 X轴正方向朝右,Y轴正方向朝上MM_HIENGLISH 0.001英寸 X轴正方向朝右,Y轴正方向朝上MM_TWIPS 1/1440英寸X轴正方向朝右,Y轴正方向朝上MM_ISOTROP
9、IC 系统确定 X、Y轴可任意调节,X、Y轴比例为1:1MM_ANISOTROC 系统确定 X、Y轴可任意调节,X、Y轴比例任意设置原点:视口的缺省原点和窗口的缺省原点均为(0,0)通过调用函数 CDC:SetWindowOrg()设置设备环境的窗口原点的坐标,调用CDC:SetViewportOrg()重新设置设备的视口原点的坐标。窗口:对应逻辑坐标系上程序员设定的区域,视口:对应实际输出设备上程序员设定的区域窗口原点是指逻辑窗口坐标系的原点在视口(设备)坐标系中的位置,视口原点是指设备实际输出区域的原点。除了映射模式,窗口和视口也是决定一个点的逻辑坐标如何转换为设备坐标的一个因素。一个点的
10、逻辑坐标按照如下式子转换为设备坐标:设备(视口)坐标 = 逻辑坐标 窗口原点坐标 + 视口原点坐标绘图模式:1,逻辑坐标映射为设备坐标(设置原点) 。2,直接在设备坐标中绘图。坐标映射:设坐标方向为向右,向下,SetWindowOrg(50,50),则:逻辑坐标(50,50)映射为设备坐标(0,0) 。即逻辑坐标中图形向左向上进行了平移。故窗口原点的设置原则:设为与要移动方向相反方向。A(0,0)A(50,50)逻辑坐标 设备坐标逻辑坐标 设备坐标例:填空:将如上图中逻辑坐标中图形移到设备坐标中相应位置,则:SetWindowOrg( _, _ )例 分别在 OnDraw()函数中添加如下代码
11、,设置不同的窗口原点和视口原点,结果有什么不同。 (1) pDC-SetMapMode(MM_TEXT); pDC-Rectangle(CRect(50, 50, 100, 100);(2) pDC-SetMapMode(MM_TEXT); pDC-SetWindowOrg(50, 50);pDC-Rectangle(CRect(50, 50, 100, 100);(3) pDC-SetMapMode(MM_TEXT); pDC-SetViewportOrg(50,50);pDC-Rectangle(CRect(50, 50, 100, 100);(4) pDC-SetMapMode(MM_T
12、EXT); pDC-SetViewportOrg(50,50);pDC-SetWindowOrg(50, 50);pDC-Rectangle(CRect(50, 50, 100, 100);设备环境类对象的其它成员函数:颜色设置设置背景色的成员函数 CDC:SetBkColor()设置文本颜色的成员函数 CDC:SetTextColor()。例如:COLORREF rgbBkClr=RGB(192,192,192); / 定义灰色pDC-SetBkCorlor(rgbBkClr); / 背景色为灰色pDC-SetTextColor(RGB(0,0,255); / 文本颜色为兰色说明: Wind
13、ows 用 COLORREF 类型的数据存放颜色,它是一个 32 位整数。任何一种颜色都是由红、绿、蓝三种基本颜色组成,COLORREF 类型数据的低位字节存放红色强度值,第 2 个字节存放绿色强度值,第 3 个字节存放蓝色强度值,高位字节为 0,每一种颜色分量的取值范围为 0 到 255。 直接设置 COLORREF 数据不太方便,Windows 提供了 RGB 宏用于设置颜色,将其中的红、绿、蓝分量值转换为 COLORREF 类型的颜色数据: RGB(byRed, byGreen, byBlue),其中参数 byRed、byGreen 和 byBlue 分别表示红、绿、蓝分量值(范围 0
14、到255) 。红色值:RGB(255,0,0)蓝色值:RGB(0,255,0)绿色值:RGB(0,0,255)三、GDI 对象:GDI 对象是 Windows 图形设备接口的抽象绘图工具。除了画笔和画刷,其它 GDI 对象还包括字体、位图和调色板。MFC 对 GDI 对象进行了很好的封装,提供了封装 GDI 对象的类,如 CPen、CBrush、CFont、CBitmap 和 CPalette 等,这些类都是 GDI 对象类CGdiObject 的派生类。 CDC 类提供了成员函数 SelectObject()选择用户自己创建的 GDI 对象。该函数有多种重载形式,可以选择用户已定制好的画笔、
15、画刷、字体和位图等不同类型的 GDI 对象。CPen* SelectObject(CPen* pPen); CPen *poldpen=pdc. SelectObject( CBrush* SelectObject(CBrush* pBrush); virtual CFont* SelectObject(CFont* pFont); 绘图输出的一般过程:1, 创建设备环境对象, CClientDC pdc(this);2, 创建 GDI 对象(绘图工具),CPen pen; pen.CreatGetStockObject(BLACK_PEN);3, 设备环境对象选择某些 GDI 对象,pdc.
16、 SelectObject(4, 设备环境对象使用 GDI 对象调用绘图函数输出。pdc.ellipse();(1) 画笔的创建第一步:使用画笔之前必须事先定义一个画笔对象(API:句柄)形式如下: CPen pen /HPEN hP;第二步:然后调用成员函数 CreatStockObject 获取 Windows 系统定义的四种画笔或 创建用户自定义新画笔。获取系统画笔 BLACK_PEN 的形式如下:/hP=GetStockObject(BLACK_PEN); pen.CreatGetStockObject(BLACK_PEN);画笔样式:WHITE_PEN,BLACK_PEN,DC_PE
17、N,NULL_PEN创建新画笔,形式如下:Font. CreatePen(.); /hP=CreatePen(int nPenStyle, /确定画笔样式 int nWidth, /画笔宽度COLORREF rgbColor /画笔颜色); 画笔样式有:PS_DASH: 虚线PS_DASHDOT: 点划线PS_DASHDOTDOT: 双点划线PS_DOT: 点线PS_INSIDEFRAME: 实线PS_NULL: 无PS_SOLID: 实线注:不再使用当前画笔时,需删除画笔,以免占内存font.DeleteObject( ) API 中: DeleteObject(hP);(2) 画刷的创建第
18、一步:使用画刷需事先定义一个画刷类对象。形式如下:/ HBRUSH hBr; /hBr 为画刷句柄CBrush brush; 第二步:然后调用函数 CreateStockObject 获取 Windows 系统提供的 7 种画刷/ hBr=(HBRUSH)GetStockObject(nBrushStyle)画刷样式brush. CreateStockObject(nBrushStyle)画刷样式:BLACK_BRUSH 黑色画刷,DKGRAY_BRUSH 深灰色画刷GRAY_BRUSH 灰色画刷,HOLLOW_BRUSH 虚画刷LTGRAY_BRUSH亮灰色画刷,NULL_BRUSH 空画刷
19、WHITE_BRUSH 白色画刷指定颜色画刷如何获得?可调用函数 CreateSolidBrush 和 CreateHatchBrush 创建具有指定颜色的单色画刷,创建指定阴影图案和颜色的画刷。brush. CreateSolidBrush(rgbColor); 注: 删除画刷。不使用画刷时,可用 brush. DeleteObject();删除画刷,释放内存四、 常用绘图函数(由设备环境类对象 CClient pdc(this)调用 1设置画笔当前位置的函数 MoveToEx,/ pdc. MoveToEx / BOOL MoveToEx( / HDC hdc,int X,Y, / X、Y
20、 分别为新位置的逻辑坐标LPPOINT lpPoint /存放原画笔位置的 POINT 结构地址)2 从当前位置向指定坐标点画直线的函数 LineToEx,BOOL LineToEx(HDC hdc,int X,int Y) /X 和 Y 为线段的终点坐标3 从当前位置开始,依次用线段连接 lpPoints 中指定的各点. pdc. Polyline / BOOL Polyline( / HDC hdc,LPPOINT lpPoints, /指向包含各点坐标的 POINT 结构数组的指针int nCount / nCount 为 POINT 数组中点的个数)4 绘制椭圆弧线的函数 Arc,BO
21、OL Arc(HDC hdc,int X1,intY1, /边框矩形左上角的逻辑坐标int X2,int Y2, /边框矩形右下角的逻辑坐标int X3,int Y3, /椭圆弧起始点坐标int X4,int Y4 /椭圆弧终止点坐标) 5 绘制饼图,并用当前画刷进行填充BOOL Pie(HDC hdc,int X1,intY1, /边框矩形左上角的逻辑坐标int X2,int Y2, /边框矩形右下角的逻辑坐标int X3,int Y3, /椭圆弧起始经线的确定点坐标int X4,int Y4 /椭圆弧终止经线的确定点坐标)6 绘制矩形,并用当前画刷进行填充BOOL Rectangle(HD
22、C hdc,int X1,int Y1,int X2,int Y2)(X1,Y1 )和(X2,Y2)分别为矩形的左上角和右下角的逻辑坐标7 绘制圆角矩形,并用当前画刷填充BOOL RoundRect (HDC hdc,int X1,int Y1,int X2,int Y2, int nHeight, int nWidth)圆角的高度和宽度8 绘制椭圆,并用当前画刷填充BOOL Ellipse(HDC hdc,intX1,intY1,intX2,intY2)9绘制多边形,并用当前画刷填充BOOL Polygon(HDC hdc,LPPOINT lpPoints,int nCount)包含各点坐标
23、的 POINT 数组的地址多边形点的个数四、 应用实例【例】利用绘图函数创建填充区。共有三个填充图形,第一个是用深灰色画刷填充带圆角的矩形,第二个是采用亮灰色画刷填充一个椭圆型图,第三个是用虚画刷填充饼形图。使用虚画刷填充时,看不出填充效果!draw1Dlg.cpp void CDraw1Dlg:OnPaint() if (IsIconic().else CDialog:OnPaint();CClientDC pdc(this);CRect rc;this-GetClientRect(pdc.Rectangle(rc.left,rc.top,rc.right,rc.bottom);CPen p
24、en; CBrush brush,brush1;pdc.SetMapMode(MM_ANISOTROPIC); /设置映像模 pen.CreateStockObject(BLACK_PEN);/GetStockObject(BLACK_PEN); /黑色画笔brush.CreateStockObject(DKGRAY_BRUSH); / GetStockObject(DKGRAY_BRUSH); /画刷pdc.SelectObject(brush); /选择画刷 hDC,pdc.SelectObject(pen); /选择画笔 hDC, pdc.RoundRect(50,120,100,200
25、,15,15); /绘制圆角矩形brush1.CreateStockObject(LTGRAY_BRUSH); /采用亮灰色画刷pdc.SelectObject(brush1); /选择画刷pdc.Ellipse(150,50,200,150); /绘制椭圆brush.CreateStockObject(HOLLOW_BRUSH); /虚画刷pdc.SelectObject(brush); /选择画刷pdc.Pie(250,50,300,100,250,50,300,50); /绘制饼形动态图形:用户区窗口图形的刷新:常用用户区窗口刷新机制:当用户区的内容需要刷新时,系统向应用程序消息队列发送
26、WM_PAINT 消息,系统在应用程序的消息队列中加入该消息,以通知窗口函数执行刷新处理。应用程序接收到该消息后,调用 On_Paint()函数实现窗口图形的重画。刷新请求的两种方式 :下列事件,系统会自动窗口刷新请求:用户区移动或显示,用户窗口大小改变,程序通过滚动条滚动窗口,窗口被另一个窗口覆盖的,恢复如下拉式菜单关闭等,光标穿过用户区,图标拖过用户区。用户的刷新请求:调用窗口类函数:Invalidate(true)或 InvalidateRect()【例】编写一个程序,在屏幕上出现一个圆心沿正弦曲线轨迹移动的实心圆,而且,每隔四分之一周期,圆的填充色和圆的周边颜色都发生变化,同时,圆的半
27、径在四分之一周期之内由正弦曲线幅值的 0.2 倍至 0.6 倍线性增长。(1) 正弦曲线的表示:窗口初始化时,生成正弦曲线各点的坐标。把正弦曲线一个周期的横坐标分成 100 个等分点,存储在数组 lpSin100中,100 个点的坐标计算如下:for(int j=0;j100;j+) /生成正弦曲线的点坐标 /POINT psSin100 lpSinj.x=(long)(j*2*Pi/100*60);/ 正弦曲线宽放大 60 倍lpSinj.y=(long)(dfRange*sin(j*2*Pi/100); 高放大 dfRange=100 倍(2) 动态显示圆在正弦曲线上移动数组 lpSin1
28、00的长度为 100,设定圆在正弦曲线移动时共有 100 个位置数组中每一个值是圆移动时圆心的坐标,每四分之一周期有 25 个位置i=25 处于第 1 个 1/4 周期,创建红色画笔和画刷;25i50 处于第 2 个 1/4 周期,创建绿色画笔和画刷;50i75 处于第 3 个 1/4 周期,创建蓝色画笔和画刷;75i100处于第 4 个 1/4 周期,创建黄色画笔和画刷;CPen pen;Cbrush brush;if(i=25) /第一个 1/4 周期/hPen=CreatePen(PS_DASH,1,RGB(255,0,0);pen. CreatePen(PS_DASH,1,RGB(25
29、5,0,0); /hBrush=CreateHatchBrush(HS_BDIAGONAL,RGB(255,0,0);brush. CreateHatchBrush(HS_BDIAGONAL,RGB(255,0,0);lRadious=(long)(dfRange*0.2+i%25*dfRange*0.4/25);/计算半径在消息 WM_PAINT 处理程序中,由此经过线性差分计算圆半径的大小 lRadious,第 1 个1/4 周期的程序代码如下:创建的画笔和画刷选入设备环境后,调用函数 Ellipse()绘制圆形(3)动态显示的关键:Sleep(100); /停 0.1 秒。 调用 Sle
30、ep(100)函数使程序暂停 0.1 秒。所含参数 100代表暂停的时间,使用毫秒作单位。if(i100) InvalidateRect(hWnd,NULL,1); /刷新用户区。 i100 时调用函数刷新用户区发送 WM_PAINT 消息,刷新整个用户区,清除用户区中所有的显示内容具体操作:一,定义变量:class CTuyuanDlg : public CDialog/ Constructionpublic:CTuyuanDlg(CWnd* pParent = NULL); / standard constructorpublic:double dfTheta,dfRange; /正弦曲线
31、的宽高放大倍数 .long i,j;long lCentreX,lCentreY,lRadious;/定义圆心坐标和圆半径.POINT lpSin100; /定义正弦曲线的点坐标位置:在对话框类对象定义中(即头文件中) ,做为该类的成员。或在 CText1Dlg.h 加入二,初始化变量:头文件中 :#define pi 3.14159编译文件中:#inlcude “math.h”dfTheta=60, dfRange=100.0; /正弦曲线的宽高放大倍数 .i=0,j=0;lCentreX=0,lCentreY=0,lRadious=(long)(0.2*dfRange);for(int j
32、=0;j100;j+) /生成正弦曲线的点坐标lpSinj.x=(long)(j*2*Pi/100*dfTheta);lpSinj.y=(long)(dfRange*sin(j*2*Pi/100);位置:在对话框类对象的构造函数或初始化函数中:CText1Dlg:OnInitDialog()三,绘图:位置:在对话框类对象的绘图函数中:void CText1Dlg:OnPaint()CClientDC pdc(this);CPen pen,*mypen, pen1;CBrush brush,*mybrush;pdc.SetWindowOrg(-50,-150); /pdc.SetWindowEx
33、t(-200,-200);/设置原点坐标pen1.CreatePen(PS_DASH,1,RGB(255,0,0); /建新画笔mypen=pdc.SelectObject( /选入画笔pdc.Polyline(lpSin,100); / fabs(sin(i*2*Pi/100) *dfRange*0.4if(i=25) /第一个 1/4 周期 . pen.CreatePen(PS_DASH,1,RGB(255,0,0);brush.CreateHatchBrush(HS_BDIAGONAL,RGB(255,0,0);/mypen=/mybrush=lRadious=(long)(dfRang
34、e*0.2+i%25*dfRange*0.4/25); /计算半径else if(i=50)/第二个 1/4 周期. pen.CreatePen(PS_DASH,1,RGB(0,255,0);brush.CreateHatchBrush(HS_DIAGCROSS,RGB(0,255,0);/mypen=/mybrush=lRadious=(long)(dfRange*0.2+i%25*dfRange*0.4/25);else if(i=75)/第三个周期. pen.CreatePen(PS_DASH,1,RGB(0,0,255);brush.CreateHatchBrush(HS_CROSS,
35、RGB(0,0,255);/mypen=/mybrush=lRadious=(long)(dfRange*0.2+i%25*dfRange*0.4/25);else/第四个周期. pen.CreatePen(PS_DASH,1,RGB(255,255,0);brush.CreateHatchBrush(HS_VERTICAL,RGB(255,255,0);/mypen=/mybrush=lRadious=(long)(dfRange*0.2+i%25*dfRange*0.4/25);mybrush=pdc.SelectObject(/选入画刷.Mypen=pdc.SelectObject( /
36、选入画笔.lCentreX=lpSini.x; /圆心 x 坐标.lCentreY=lpSini.y; /圆心 y 坐标.pdc.Ellipse(lCentreX-lRadious,lCentreY-lRadious,lCentreX+lRadious,lCentreY+lRadious); /画圆i+;Sleep(100); /停 0.1 秒If(i100) InvalidateRect(NULL,true);思考:当半径取:lRadious=(long)(dfRange*0.2+fabs(sin(i*2*Pi/100) *dfRange*0.4); 运行结果有何不同。作 业: P102:1, 2,4,6 教学总结: