GDI编程

GDI(Graphics Device Interface)是Windows系统中用于应用程序与输出设备交互的接口,提供了设备无关的编程环境。本文详细介绍了GDI的概念,如设备描述表DC、GDI对象的创建和使用,以及如何通过DC进行绘图。内容涵盖DC的获取、GDI对象(如画笔、画刷)的使用、文本和图形的绘制,并提供了具体的代码实例,展示了在Win32和MFC中如何进行图形编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GDI编程

一、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参数               含义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值