编程模型中的更改

以下各节介绍使用 Windows GDI+ 编程与使用 Windows 图形设备接口 (GDI) 编程的几种方法不同。

设备上下文、句柄和图形对象

如果你已使用 GDI (早期版本的 Windows) 中包含的图形设备界面编写程序,则你熟悉设备上下文 (DC) 。 设备上下文是 Windows 用来存储有关特定显示设备功能和属性的信息的结构,这些属性指定如何在该设备上绘制项。 视频显示的设备上下文也与显示器上的特定窗口相关联。 首先, (HDC) 获取设备上下文的句柄,然后将该句柄作为参数传递给实际执行绘图的 GDI 函数。 还可以将句柄作为参数传递给获取或设置设备上下文属性的 GDI 函数。

使用 GDI+时,不必像使用 GDI 时那样关注句柄和设备上下文。 只需创建 Graphics 对象,然后以熟悉的面向对象的样式(myGraphicsObject.DrawLine (参数) )调用其方法。 Graphics 对象位于 GDI+ 的核心,就像设备上下文位于 GDI 的核心一样。 设备上下文和 Graphics 对象扮演类似的角色,但与设备上下文 (GDI) 一起使用的基于句柄的编程模型与用于 Graphics 对象的面向对象的模型之间存在一些根本差异, (GDI+) 。

与设备上下文一样, Graphics 对象与屏幕上的特定窗口相关联,并包含 (属性,例如,平滑模式和文本呈现提示) 指定项目绘制方式。 但是,与设备上下文一样, Graphics 对象不绑定到笔、画笔、路径、图像或字体。 例如,在 GDI 中,必须先调用 SelectObject 以将笔对象与设备上下文关联,然后才能使用设备上下文来绘制线条。 这称为在设备上下文中选择笔。 在设备上下文中绘制的所有线条都将使用该笔,直到选择其他触控笔。 使用 GDI+,可将 Pen 对象作为参数传递给 Graphics 类的 DrawLine 方法。 可以在一系列 DrawLine 调用中的每个中使用不同的 Pen 对象,而无需将给定的 Pen 对象与 Graphics 对象相关联。

绘制线条的两种方法

以下两个示例分别绘制一条宽度为 3 的红线,从位置 (20,10) 到位置 (200,100) 。 第一个示例调用 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::D rawLine 方法。 Graphics::D rawLine 方法的第一个参数是指向 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 对象创建和维护。 也可以独立于 Graphics 对象创建和维护 BrushGraphicsPathImageFont 对象。 Graphics 类提供的许多绘图方法接收 BrushGraphicsPathImageFont 对象作为参数。 例如, Brush 对象的地址作为参数传递到 FillRectangle 方法, GraphicsPath 对象的地址作为参数传递到 Graphics::D rawPath 方法。 同样, ImageFont 对象的地址将传递给 DrawImageDrawString 方法。 这与 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 对象的指针、起点的坐标和终点的坐标。 前两个变体以浮点数的形式接收坐标,最后两个变体以整数形式接收坐标。 第一个和第三个变体接收坐标作为四个独立数字的列表,而第二个和第四个变体接收坐标作为 一对 Point (或 PointF) 对象。

不再有当前位置

请注意,在前面显示的 DrawLine 方法中,线条的起点和终点都作为参数接收。 这与 GDI 方案不一致,在 GDI 方案中,调用 MoveToEx 设置当前笔位置后跟 LineTo ,以便绘制一条从 (x1y1) 开始,到 (x2y2) 结束的线条。 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+ 中的 FillRectangleDrawRectangle 方法接收指定矩形的左边缘、顶部、宽度和高度的参数。 这与 GDIRectangle 函数形成鲜明对比,后者采用指定矩形的左边缘、右边缘、顶部和底部的参数。 另请注意,GDI+ 中 Color 类的构造函数有四个参数。 最后三个参数是通常的红色、绿色和蓝色值;第一个参数是 alpha 值,它指定所绘制的颜色与背景色混合的程度。

构造区域

GDI 提供了多个用于创建区域的函数:CreateRectRgn、CreateEllpticRgn、CreateRoundRectRgn、CreatePolygonRgn 和 CreatePolyPolygonRgn。 你可能希望 GDI+ 中的 Region 类具有类似的构造函数,这些构造函数采用矩形、椭圆形、圆角矩形和多边形作为参数,但情况并非如此。 GDI+ 中的 Region 类提供接收 Rect 对象引用的构造函数和接收 GraphicsPath 对象地址的另一个构造函数。 如果要基于椭圆、圆角矩形或多边形构造区域,可以通过创建包含椭圆的 GraphicsPath 对象 (轻松执行此操作,例如) 然后将该 GraphicsPath 对象的地址传递给 Region 构造函数。

GDI+ 通过组合形状和路径可以轻松形成复杂区域。 Region 类具有 UnionIntersect 方法,可用于使用路径或其他区域扩充现有区域。 GDI+ 方案的一个不错功能是 ,将 GraphicsPath 对象作为参数传递给 Region 构造函数时不会销毁它。 在 GDI 中,可以使用 PathToRegion 函数将路径转换为区域,但该路径在此过程中被销毁。 此外, 当 GraphicsPath 对象的地址作为参数传递到 Union 或 Intersect 方法时,不会销毁该对象,因此可以使用给定路径作为多个独立区域的构建基块。 下面的示例说明了这一点。 假设 onePath 是指向 GraphicsPath 对象的指针, (已初始化的简单或复杂) 。

Region  region1(rect1);
Region  region2(rect2);
region1.Union(onePath);
region2.Intersect(onePath);