GDI绘图
GDI是Graphics Device Interface的缩写,称为图形设备接口,主要用来绘图,由动态链接库GDI32.DLL提供支持。
GDI就是一个函数库,提供了很多绘图函数(也就是GDI32.DLL中的导出函数)。GDI非常重要,不但应用程序使用它来绘图,Windows本身也使用GDI来显示用户界面,比如菜单、图标和鼠标指针等。
由mingw编译的WIN32 GDI绘图程序在链接时需要加参数-lgdi32
DC
Device Contexts
A device context is a structure that defines a set of graphic objects and their associated attributes, as well as the graphic modes that affect output. The graphic objects include a pen for line drawing, a brush for painting and filling, a bitmap for copying or scrolling parts of the screen, a palette for defining the set of available colors, a region for clipping and other operations, and a path for painting and drawing operations. The remainder of this section is divided into the following three areas.
设备上下文允许在 Windows 中进行与设备无关的绘制。设备上下文可用于绘制到屏幕、打印机或者图元文件。
Windows应用程序通过为指定设备(屏幕,打印机等)创建一个设备描述表(Device Context, DC)在DC表示的逻辑意义的“画布”上进行图形的绘制。DC是一种包含设备信息的数据结构,它包含了物理设备所需的各种状态信息 。Win32程序在绘制图形之前需要获取DC的句柄HDC,并在不继续使用时释放掉。
获取DC
GetDC
要得到窗口显示区域的设备内容句柄,可以使用GetDC来取得句柄。GetDC 函数检索指定窗口或整个屏幕的工作区的设备上下文 (DC) 的句柄。 可以在后续 GDI 函数中使用返回的句柄在 DC 中绘制。 设备上下文是一个不透明的数据结构,其值由 GDI 在内部使用。
使用通用DC进行绘制后,必须调用 ReleaseDC 函数来释放 DC
类和专用 DC 不必ReleaseDC
必须从调用 GetDC 的同一线程调用 ReleaseDC
DC 的数量仅受可用内存的限制。
Win32API
MFC版
GetWindowDC函数
返回hWnd参数所指定的窗口的设备环境。获得的设备环境覆盖了整个窗口(包括非客户区),例如标题栏、菜单、滚动条,以及边框。这使得程序能够在非客户区域实现自定义图形,例如自定义标题或者边框。当不再需要该设备环境时,需要调用ReleaseDC函数释放设备环境。
BeginPaint 和 EndPaint 函数
在工作区中准备和完成绘图。 BeginPaint 返回用于在工作区中绘制的显示设备上下文的句柄; EndPaint 结束画图请求并释放设备上下文。
LRESULT APIENTRY WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 0, 0, "Hello, Windows!", 15);
EndPaint(hwnd, &ps);
return 0L;
// Process other messages.
}
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
hwnd = CreateWindowEx(
// parameters
);
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
return msg.wParam;
}
BeginPaint 函数准备用于绘制的指定窗口,并使用有关绘图的信息填充 PAINTSTRUCT 结构。
BeginPaint 函数自动设置设备上下文的剪辑区域,以排除更新区域以外的任何区域。 更新区域由 InvalidateRect 或 InvalidateRgn 函数设置,并在调整大小、移动、创建、滚动或影响工作区的任何其他操作后由系统设置。 如果更新区域标记为擦除, BeginPaint 会将 WM_ERASEBKGND 消息发送到窗口。
BeginPaint 自动清除窗口的无效区域。应用程序不应调用 BeginPaint ,除非响应 WM_PAINT 消息。 对 BeginPaint 的每个调用都必须具有对 EndPaint 函数的相应调用。
如果插入符号位于要绘制的区域, BeginPaint 会自动隐藏插入符号,以防止擦除它。
如果窗口的类具有背景画笔, BeginPaint 使用该画笔在返回之前擦除更新区域的背景。
EndPaint 函数标记指定窗口中绘制的结尾。 每次调用 BeginPaint 函数时都需要此函数,但仅在绘制完成后才需要此函数。
如果 BeginPaint 隐藏了插入点, EndPaint 会将插入点还原到屏幕。
EndPaint 释放 BeginPaint 检索到的显示设备上下文。
DC的释放
The application must call the ReleaseDC function for each call to the GetWindowDC function and for each call to the GetDC function that retrieves a common DC.
An application cannot use the ReleaseDC function to release a DC that was created by calling the CreateDC function; instead, it must use the DeleteDC function. ReleaseDC must be called from the same thread that called GetDC.
每次调用GetWindowDC函数和GetDC函数(检索公共DC)都必须调用ReleaseDC函数。必须在调用GetDC的同一线程调用ReleaseDC。
CreateDC函数创建的DC必须使用DeleteDC函数释放。不能使用ReleaseDC函数来释放通过调用CreateDC函数创建的DC,
DeleteDC函数通常用于删除使用 CreateDC、CreateIC 或 CreateCompatibleDC 创建的设备上下文。
GetDC获得的设备上下文句柄,必须调用ReleaseDC 来释放设备上下文。不能使用DeleteDC进行删除。提供了CClientDC 和 CWindowDC 类来包装此功能。
HDC和四个DC类
继承关系

CDC和HDC
CDC是MFC的DC的一个类,它封装了几乎所有的关于HDC的操作。HDC是DC的句柄,API中的一个类似指针的数据类型,是设备描述句柄。CDC是所有MFC的DC的基类。常用的CClientDC就是CDC的子类(或称派生类),CDC等设备上下分类,都含有一个类的成员变量m_nHdc,即HDC类型的句柄
CPaintDC
无效区DC。CPaintDC 对象封装 Windows 的通用习惯用法, CPaintDC 构造函数为你调用 BeginPaint,析构函数调用 EndPaint。 简化的过程是创建 CDC 对象、绘制然后销毁 CDC 对象。 在框架中,该过程的大部分甚至整个过程都是自动化的。
一般用在OnPaint内以响应Windows消息WM_PAINT, 自动完成绘制,在整个窗口内进行重画,维持原有窗口完整性。使用CPaintDC绘制图像。它在构造期间执行CWnd::BeginPaint,在析构期间执行CWnd::EndPaint。
void CtDlg::OnPaint()
{
CPaintDC dc(this);
dc.FillRect(CRect(100,50,300,200),&CBrush(RGB(30,38,156)));
}

对应的Win32的消息:WM_PAINT事件的处理:
PAINTSTRUCT ps;
HDC hdc;
case WM_PAINT;
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
...
EndPaint(hWnd, &ps);
break;
通过使用InvalidateRect函数使指定区域无效,并发送WM_PAINT消息。
BOOL InvalidateRect(
[in] HWND hWnd,
[in] const RECT *lpRect,
[in] BOOL bErase
);
[in] hWnd
更新区域已更改的窗口的句柄。 如果此参数为 NULL,系统会使所有窗口失效并重新绘制,而不仅仅是此应用程序的窗口,并在函数返回之前发送 WM_ERASEBKGND 和 WM_NCPAINT 消息。 不建议将此参数设置为 NULL 。
[in] lpRect
指向 RECT 结构的指针,该结构包含要添加到更新区域的矩形的客户端坐标。 如果此参数为 NULL,则整个工作区将添加到更新区域。
[in] bErase
指定在处理更新区域时是否要擦除更新区域中的背景。 如果此参数为 TRUE,则调用 BeginPaint 函数时将清除背景。 如果此参数为 FALSE,则后台保持不变。
CClientDC
客户区DC,CClientDC 对象封装使用仅表示窗口工作区的设备上下文。 CClientDC 构造函数调用 GetDC 函数,析构函数调用 ReleaseDC 函数。
语法
class CClientDC : public CDC
公共构造函数
CClientDC::CClientDC 构造一个连接到 CWnd 的 CClientDC 对象。
受保护的数据成员
CClientDC::m_hWnd 此 CClientDC 对其有效的窗口的 HWND。用于构造 CClientDC 对象的 CWnd 指针的 HWND。
CWindowDC
CWindowDC CWindowDC 对象封装表示整个窗口的设备上下文,包括其框架。在构造时调用 Windows 函数 GetWindowDC,在销毁时调用 ReleaseDC。 CWindowDC 对象访问 CWnd 的整个屏幕区域(客户端和非客户端区域)。
CWindowDC与CClientDC,CPaintDC的区别:
CWindowDC可在非客户区绘制图形,而CClientDC,CPaintDC只 能在客户区绘制图形。
CWindowDC下坐标原点是在屏幕的左上角,CClientDC,CPaintDC 下坐标原点是在客户区的左上角。
画笔和画刷
1.画笔CPen、画刷CBrush、字体CFont、位图CBitmap、调色板CPalette等都是GDI对象,通常在函数没有参数指定的情况下我们使用的是DC当前默认GDI对象。
2.使用SelectObject()来改变DC的默认GDI对象,该函数返回DC中被替换的GDI对象,我们应该保存这个旧的GDI对象,当我们不需要使用新的GDI对象的时候再使用SelectObject()将旧的GDI对象恢复到DC中。
3.还可以通过HGDIOBJ GetStockObject(int fnObject)函数来获得系统标准的GDI对象,参数可以为
BLACK_BRUSH:黑色画刷;DKGRAY_BRUSH:暗灰色画刷;
DC_BRUSH:在Windows98,Windows NT 5.0和以后版本中为纯颜色画刷,缺省色为白色,可以用SetDCBrushColor函数改变颜色,更多的信息参见以下的注释部分。
GRAY_BRUSH:灰色画刷笔;
HOLLOW_BRUSH:空画刷(相当于NULL_BRUSH);
NULL_BRUSH:空画刷(相当于HOLLOW_BRUSH);
LTGRAY_BRUSH:亮灰色画刷;
WHITE_BRUSH:白色画刷;
BLACK_PEN:黑色钢笔;
DC_PEN:在Windows98、Windows NT 5.0和以后版本中为纯色钢笔,缺省色为白色,使用SetDCPenColor函数可以改变色彩,更多的信息,参见下面的注释部分。
WHITE_PEN:白色钢笔;
ANSI_FIXED_FONT:在Windows中为固定间距(等宽)系统字体;
ANSI_VAR_FONT:在Windows中为变间距(比例间距)系统字体;
DEVICE_DEFAUCT_FONT:在WindowsNT中为设备相关字体;
DEFAULT_GUI_FONT:用户界面对象缺省字体,如菜单和对话框;
OEM_FIXED_FONT:原始设备制造商(OEM)相关固定间距(等宽)字体;
SYSTEM_FONT:系统字体,在缺省情况下,系统使用系统字体绘制菜单,对话框控制和文本;
SYSTEM_FIXED_FONT:固定间距(等宽)系统字体,该对象仅提供给兼容16位Windows版本;
DEFAULT_PALETTE:缺省调色板,该调色板由系统调色板中的静态色彩组成。
GetStockObject返回的类型为HGDIOBJ,所以在使用的时候还应该将其返回值强制转换为我们所使用的GDI句柄类型,如HBRUSH、HFONT、HPALETTE等。
4.还可以使用各GDI类型的静态成员函数FromHandle()由这个GDI句柄获得其对象指针。比如CBrush::FromHandle()可以由画刷句柄获得画刷对象的指针,它是一个静态成员函数。同理,对于CPen、CFont、CBitmap、CPalette等GDI对象,甚至CDC和CWnd等也包含静态成员函数FromHandle()。实际上,MFC对各种包含内核对象的封装类都有FromHandle(HANDLE h)方法。
画笔,绘制线条与轮廓
MFC
CPen(Int style,int width ,COLORREF color);
style:画笔样式,可以为以下样式:
PS_SOLID 实线
PS_DASH 虚线,该值只有当画笔宽度等于1个设备单位或更小时才有效
PS_DOT 点线,该值只有当画笔宽度等于1个设备单位或更小时才有效
PS_DASHDOT 点和虚线交替,该值只有当画笔宽度等于1个设备单位或更小时才有效
PS_DASHDOTDOT 双点线和虚线交替,该值只有当画笔宽度等于1个设备单位或更小时才有效
PS_NULL 空画笔
PS_GEOMETRIC 几何画笔
.......
width:画笔宽度。
color:画笔颜色。
获取画笔与释放
COLORREF color = RGB(255, 0, 0);//颜色
CPen pen(PS_SOLID, 3, color);
...
//画笔使用完成后使用DeleteObject释放掉资源
DeleteObject(pen);
MFC的窗体中绘制两个矩形
//创建画笔
COLORREF color = RGB(255, 0, 0);
CPen pen(PS_SOLID, 3, color);
//获取dc
CDC *pdc = GetDC();
//选择画笔,保存旧画笔
CPen *oldPen = pdc->SelectObject(&pen);
//绘图
pdc->Rectangle(30,70,200,200);
pdc->Rectangle(100,100,300,210);
//恢复旧的画笔
pdc->SelectObject(oldPen);
//释放画笔资源
DeleteObject(pen);
//释放dc
ReleaseDC(pdc);

Win32API
HPEN CreatePen(int nPenStyle, int nWidth, COLORREF crColor);
的是实线
PS_DASH
画笔画出的是虚线(nWidth必须不大于1)
PS_DOT
画笔画出的是点线(nWidth必须不大于1)
PS_DASHDOT
画笔画出的是点划线(nWidth必须不大于1)
PS_DASHDOTDOT
画笔画出的是点-点-划线(nWidth必须不大于1)
PS_NULL
画笔不能画图
PS_INSIDEFRAME
由椭圆、矩形、圆角矩形、饼图以及弦等生成的封闭对象框时,画线宽度向内扩展。如指定的准确RGB颜色不存在,就进行抖动处理
nWidth --------- Long,以逻辑单位表示的画笔的宽度
crColor -------- Long,画笔的RGB颜色
创建画笔与释放
使用WIN32API绘制一个三角形
HDC hdc = ::GetDC(m_hWnd) ;
HPEN hPen = CreatePen(PS_SOLID, 2,RGB(255,0,0));
HPEN oldPen = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc,100,100,NULL);
LineTo(hdc,300,100);
LineTo(hdc,400,200);
LineTo(hdc,100,100);
SelectObject(hdc,oldPen);
::DeleteObject(hPen);
::ReleaseDC(m_hWnd, hdc) ;

画刷,填充封闭区域,绘制背景
MFC
绘制圆角矩形
CDC *pdc = GetDC();
CBrush brush(RGB(0, 0, 255));
CBrush *oldBrush = pdc->SelectObject(&brush);
//绘制圆角矩形
pdc->RoundRect(CRect(50, 50, 250, 130),CPoint(50,50));
pdc->SelectObject(oldBrush);
DeleteObject(brush);
ReleaseDC(pdc);

解决绘制的图形互相覆盖的问题

设置透明画刷NULL_BRUSH
CDC *pdc = GetDC();
pdc->SelectObject(GetStockObject(NULL_BRUSH));
pdc->Rectangle(30,70,200,200);
pdc->Rectangle(100,100,300,210);
ReleaseDC(pdc);

MFC中CBrush画刷的构造函数:
CBrush( COLORREF crColor ); //普通画刷
CBrush( CBitmap* pBitmap ); //位图画刷
CBrush( int nIndex, COLORREF crColor ); //阴影线画刷
nIndex:阴影线的风格,有以下风格可选:
HS_HORIZONTAL 水平的阴影线
HS_VERTICAL 垂直的阴影线
HS_CROSS 水平和垂直方向以网格线作出阴影
HS_BDIAGONAL 45度的向下影线(从左到右)
HS_FDIAGONAL 45度的向上阴影线(从左到右)
HS_DIAGCROSS 45度的网格线阴影
对应的Win32API创建画刷的函数
CreateSolidBrush() 用指定的颜色初始化画刷。
CreateHatchBrush() 用指定的阴影线初始化画刷。
CreateBrushIndirect() 用结构LOGBRUSH中指定的风格、颜色和模式初始化画刷。
CreatePatternBrush() 用位图指定的模式初始化画刷。
CreateDIBPatternBrush() 用独立于设备的位图(DIB)初始化画刷。
CreateSysColorBrush() 创建一个使用系统缺省颜色的画刷。
创建带有图案的画刷
CDC *pdc = GetDC();
CBrush brush(HS_CROSS, RGB(0, 255, 255));
CBrush *oldBrush = pdc->SelectObject(&brush);
CPen pen(PS_SOLID,2,RGB(0, 255, 0));
CPen *oldPen = pdc->SelectObject(&pen);
pdc->Ellipse(CRect(50,50,250,150));
pdc->SelectObject(oldBrush);
pdc->SelectObject(oldPen);
DeleteObject(brush);
DeleteObject(pen);
ReleaseDC(pdc);

画文字
MFC在OnPaint函数中客户区文字绘制
void CdrawTextDlg::OnPaint()
{
CPaintDC dc(this);
CFont font;
font.CreatePointFont(220, L"微软雅黑");//字体
dc.SelectObject(&font);
dc.SetBkMode(TRANSPARENT);//文字背景透明
CString str = L"安装在计算机上的帮助文件";
dc.SetTextColor(RGB(255, 0, 0));//设置文字颜色
dc.TextOut(30,100,str);//画文字
}

GDI绘图函数
绘制一个像素点:SetPixel()。
绘制直线:MoveTo(),LineTo()。
绘制多个首尾相连的线:Polyline()。
绘制矩形:FrameRect(),Rectangle(),FillRect() ,FillSolidRect()。 Rectangle函数用于绘制矩形,包括绘制矩 形边框线和填充矩形,而FillRect函数只用于填充矩形内部,FrameRect函数则只画矩形的线。也就是说, Rectangle=FillRect+FrameRect。
void CDC::FillSolidRect(LPCRECT lpRect,COLORREF clr);//绘制区域,绘制颜色
void CDC::FillSolidRect(int x,int y,int cx,int cy,COLORREF clr);
绘制一个四个角是弧形的矩形:RoundRect()。
绘制圆形或椭圆:Ellipse()。
绘制弧线:Arc(),ArcTo()。
绘制三角形或多边形:Polygon()。
绘制饼形图:Pie()。
绘制文字:TextOut(),DrawText()
绘图区域更新
该函数向指定的窗体更新区域添加一个矩形,然后窗口客户区域的这一部分将被重新绘制。
BOOL InvalidateRect(
HWND hWnd, // handle of window with changed update region
CONST RECT *lpRect, // address of rectangle coordinates
BOOL bErase // erase-background flag
);
win32api用法:
当最后一个参数为false,背景不擦除绘制

当最后一个参数为true,擦除背景绘制
