编程模型中的更改
以下部分介绍了使用 Windows GDI+ 编程与使用 Windows 图形设备接口 (GDI) 编程的几种不同方式。
设备上下文、句柄和图形对象
如果你使用 GDI(以前版本的 Windows 中包含的图形设备接口)编写程序,则你熟悉设备上下文 (DC) 的概念。 设备上下文是 Windows 用来存储有关特定显示设备和属性的信息以及指定如何在该设备上绘制项目的属性的结构。 视频显示器的设备上下文也与显示器上的特定窗口相关联。 首先,获取设备上下文 (HDC) 的句柄,然后将该句柄作为参数传递给实际执行绘图的 GDI 函数。 还可以将句柄作为参数传递给 GDI 函数,以获取或设置设备上下文的属性。
使用 GDI+时,无需像使用 GDI 时那样关注句柄和设备上下文。 只需创建一个 Graphics 对象,然后以熟悉的面向对象样式 myGraphicsObject.DrawLine(parameters) 调用其方法。 正如设备上下文是 GDI 的核心一样,Graphics 对象也是 GDI+ 的核心。 设备上下文和 Graphics 对象扮演类似的角色,但与设备上下文 (GDI) 一起使用的基于句柄的编程模型和与 Graphics 对象一起使用的面向对象模型 (GDI+) 之间存在一些根本区别。
与设备上下文一样,Graphics 对象与屏幕上的特定窗口相关联,并包含指定如何绘制项目的属性(例如,平滑模式和文本渲染提示)。 但是,与设备上下文不同,Graphics 对象不与笔、画笔、路径、图像或字体绑定。 例如,在 GDI 中,在使用设备上下文绘制线条之前,必须调用 SelectObject 才能将笔对象与设备上下文相关联。 这称为在设备上下文中选择笔。 在设备上下文中绘制的所有线条都将使用该笔,直到选择其他笔。 使用 GDI+,可以将 Pen 对象作为参数传递给 Graphics 类的 DrawLine 方法。 可以在一系列 DrawLine 调用中使用不同的 Pen 对象,而无需将给定的 Pen 对象与 Graphics 对象相关联。
绘制线条的两种方法
以下两个示例分别从位置 (20, 10) 到位置 (200,100) 绘制一条宽度为 3 的红色线条。 第一个示例调用 GDI,第二个示例通过 C++ 类接口调用 GDI+。
使用 GDI 绘制线条
若要使用 GDI 绘制线条,需要两个对象:设备上下文和笔。 通过调用 BeginPaint 可以获得设备上下文的句柄,通过调用 CreatePen 可以获得笔的句柄。 接下来,调用 SelectObject 将笔选择到设备上下文中。 通过调用 MoveToEx 将笔位置设置为 (20, 10),然后通过调用 LineTo 从该笔位置画一条线到 (200, 100)。 请注意,MoveToEx 和 LineTo 都接收 hdc 作为参数。
HDC hdc;
PAINTSTRUCT ps;
HPEN hPen;
HPEN hPenOld;
hdc = BeginPaint(hWnd, &ps);
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
hPenOld = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc, 20, 10, NULL);
LineTo(hdc, 200, 100);
SelectObject(hdc, hPenOld);
DeleteObject(hPen);
EndPaint(hWnd, &ps);
使用 GDI+ 和 C++ 类接口绘制线条
若要使用 GDI+ 和 C++ 类接口绘制线条,需要 Graphics 对象和 Pen 对象。 请注意,您不需要向 Windows 请求这些对象的句柄。 而是使用构造函数创建 Graphics 类(Graphics 对象)的实例和 Pen 类实例(Pen 对象)的实例。 绘制线条涉及调用 Graphics 类的 Graphics::DrawLine 方法。 Graphics::DrawLine 方法的第一个参数是指向 Pen 对象的指针。 这是一个比在前面的 GDI 示例中选择笔进入设备上下文更简单、更灵活的方案。
HDC hdc;
PAINTSTRUCT ps;
Pen* myPen;
Graphics* myGraphics;
hdc = BeginPaint(hWnd, &ps);
myPen = new Pen(Color(255, 255, 0, 0), 3);
myGraphics = new Graphics(hdc);
myGraphics->DrawLine(myPen, 20, 10, 200, 100);
delete myGraphics;
delete myPen;
EndPaint(hWnd, &ps);
笔、画笔、路径、图像和字体作为参数
前面的示例表明,Pen 对象可以与提供绘图方法的 Graphics 对象分开创建和维护。 Brush、GraphicsPath、Image 和 Font 对象也可以与 Graphics 对象分开创建和维护。 由 Graphics 类提供的许多绘图方法都接收 Brush、GraphicsPath、Image 或 Font 对象作为参数。 例如,Brush 对象的地址作为参数传递给 FillRectangle 方法,GraphicsPath 对象的位置作为参数传递给 Graphics::DrawPath 方法。 同样,Image 和 Font 对象的地址被传递给 DrawImage 和 DrawString 方法。 这与 GDI 形成鲜明对比,在 GDI 中,您可以在设备上下文中选择画笔、路径、图像或字体,然后将句柄作为绘图函数的参数传递给设备上下文。
方法重载
许多 GDI+ 方法都重载;也就是说,多个方法共享相同的名称,但具有不同的参数列表。 例如,Graphics 类的 DrawLine 方法采用以下形式:
Status DrawLine(IN const Pen* pen,
IN REAL x1,
IN REAL y1,
IN REAL x2,
IN REAL y2);
Status DrawLine(IN const Pen* pen,
IN const PointF& pt1,
IN const PointF& pt2);
Status DrawLine(IN const Pen* pen,
IN INT x1,
IN INT y1,
IN INT x2,
IN INT y2);
Status DrawLine(IN const Pen* pen,
IN const Point& pt1,
IN const Point& pt2);
以上所有四个 DrawLine 变体都接收指向 Pen 对象的指针、起点的坐标和终点的坐标。 前两个变体以浮点数形式接收坐标,后两个变体则以整数形式接收坐标。 第一个和第三个变体以四个单独数字的列表形式接收坐标,而第二个和第四个变体以一对点(或点)对象的形式接收坐标。
不再有当前位置
请注意,在前面显示的 DrawLine 方法中,线条的起点和终点都作为参数接收。 这与 GDI 方案不同,在 GDI 方案中,您调用 MoveToEx 来设置当前笔的位置,然后调用 LineTo 来绘制一条从 (x1, y1) 开始到 (x2, y2) 结束的线。 GDI+ 作为一个整体已经放弃了当前位置的概念。
绘制和填充的单独方法
在绘制轮廓和填充矩形等形状的内部时,GDI+ 比 GDI 更灵活。 GDI 具有一个 Rectangle 函数,可以在一步中绘制轮廓并填充矩形的内部。 轮廓使用当前选定的笔绘制,内部使用当前选定的画笔填充。
hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(hdc, hBrush);
SelectObject(hdc, hPen);
Rectangle(hdc, 100, 50, 200, 80);
GDI+ 具有单独的方法来绘制轮廓和填充矩形的内部。 Graphics 类的 DrawRectangle 方法将 Pen 对象的地址作为其参数之一,而 FillRectangle 方法将 Brush 对象的地址作为其参数之一。
HatchBrush* myHatchBrush = new HatchBrush(
HatchStyleCross,
Color(255, 0, 255, 0),
Color(255, 0, 0, 255));
Pen* myPen = new Pen(Color(255, 255, 0, 0), 3);
myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30);
myGraphics.DrawRectangle(myPen, 100, 50, 100, 30);
请注意,GDI+ 中的 FillRectangle 和 DrawRectangle 方法接收指定矩形左边缘、顶部、宽度和高度的参数。 这与 GDI Rectangle 函数形成鲜明对比,后者接受指定矩形左边缘、右边缘、顶部和底部的参数。 另请注意,GDI+ 中 Color 类的构造函数具有四个参数。 最后三个参数是通常的红色、绿色和蓝色值;第一个参数是 alpha 值,它指定了绘制的颜色与背景颜色混合的程度。
构造区域
GDI 提供了多个用于创建区域的函数:CreateRectRgn、CreateEllpticRgn、CreateRoundRectRgn、CreatePolygonRgn 和 CreatePolyPolygonRgn。 你可能希望 GDI+ 中的 Region 类具有类似的构造函数,这些构造函数接受矩形、椭圆、圆角矩形和多边形作为参数,但事实并非如此。 GDI+ 中的 Region 类提供一个构造函数,该构造函数接收 Rect 对象引用,另一个构造函数接收 GraphicsPath 对象的地址。 如果要基于椭圆、圆角矩形或多边形构造区域,可以通过创建 GraphicsPath 对象(例如包含椭圆),并将该 GraphicsPath 对象的地址传递给 Region 构造函数,从而轻松执行此操作。
通过组合形状和路径,GDI+ 可以轻松形成复杂的区域。 Region 类具有 Union 和 Intersect 方法,可用于使用路径或其他区域扩充现有区域。 GDI+ 方案的一个很好的特性是,当将 GraphicsPath 对象作为参数传递给 Region 构造函数时,它不会被破坏。 在 GDI 中,可以使用 PathToRegion 函数将路径转换为区域,但在此过程中路径会被销毁。 此外,当 GraphicsPath 对象的地址作为参数传递给 Union 或 Intersect 方法时,不会销毁该对象,因此可以将给定路径用作多个单独区域的构建基块。 这在下面的示例中显示。 假设 onePath 是指向已初始化的 GraphicsPath 对象(简单或复杂)的指针。
Region region1(rect1);
Region region2(rect2);
region1.Union(onePath);
region2.Intersect(onePath);