65.9K
CodeProject 正在变化。 阅读更多。
Home

初学者 WIN32 Paint 指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (78投票s)

2002 年 4 月 5 日

CPOL

24分钟阅读

viewsIcon

399681

downloadIcon

11569

了解 Windows 如何生成 WM_PAINT 消息、管理窗口的更新区域,以及如何使用所有常见类型的设备上下文 (DC)。

Demonstration app to visualize update regions.

引言

许多开发人员都熟悉获取设备上下文 (DC) 句柄并对其进行绘图。然而,Windows 绘图的所有组成部分的结构可能存在很多困惑。关于不同 DC 的用途以及如何有效使用每个 DC 的困惑。本文将详细描述 WIN32 绘图系统的不同部分如何组合以及如何在绘图系统中使用通用 DC 和设备 DC。本文将忽略内存 DC 和图元文件 DC。

本教程还将解释 SDK 如何与 MFC 和 WTL 的每个包装类相关联。有了这些知识,开发人员就可以在 Windows 绘图中创建、缓存和使用 DC 时做出更明智的决策。教程和代码示例均以 WTL 编写,但 MFC 和 WTL 的包装类非常相似。

本文之前有一个初级教程。之前的文章涵盖了使用 DC 在窗口上绘图的基础知识。该文章标题为:WIN32 绘图指南(初级篇)

窗口剖析

窗口是 WIN32 操作系统中向用户传达信息的基本对象。窗口是由操作系统管理的系统资源,可以通过窗口句柄 (HWND) 由宿主应用程序访问。有许多信息描述一个窗口。本节将描述与绘图相关的不同特性。

物理特性

窗口中显示大部分信息的区域称为客户区。通常,此区域由一个矩形表示。可以使用此函数从窗口中检索该矩形

::GetClientRect(HWND hWnd, RECT *pRect);

通常,客户区将由窗口的一个称为非客户区的区域包围。非客户区包括窗口边框、标题和菜单(如果存在)。表示非客户区的矩形可以使用此函数从窗口中检索

::GetWindowRect(HWND hWnd, RECT *pRect);

此函数与 GetClientRect 的区别在于,返回的矩形采用屏幕坐标,并且该矩形还包含窗口的客户区。因此,如果应用程序只想访问表示窗口客户区的区域,则必须执行此代码

RECT rWindow;
RECT rClient;

HRGN hRgnWindow;
HRGN hRgnClient;
HRGN hNCRgn;

//C: Get the window and client rectangles for the window.
::GetWindowRect(hWnd, &rWindow);
::GetClientRect(hWnd, &rClient);

//C: Translate the Client rectangle into screen coordinates.
POINT pt = {0,0};
::MapWindowPoints(hWnd, NULL, &pt, 1);
::OffsetRect(&rClient, pt.x, pt.y);

//C: Create regions from these two rectangles.
hRgnWindow = ::CreateRectRgnIndirect(&rWindow);
hRgnClient = ::CreateRectRgnIndirect(&rClient);
hNCRgn	   = ::CreateRectRgn(0,0,0,0);	

//C: Subtract the client region from the window region.
::CombineRgn(hNCRgn, hWindowRgn, hClientRgn, RGN_DIFF);

//C: Perform actions on the NC region.
...

//C: Free region resources.
::DeleteObject(hRgnWindow);
::DeleteObject(hRgnClient);
::DeleteObject(hNCRgn);

这是一张图片,说明了窗口的不同区域

Client vs Non-client window regions

更新区域

WIN32 内核管理窗口的状态。窗口最重要的元素之一是其更新区域。更新区域会记住窗口中所有需要重新绘制的部分。区域可能需要重新绘制的原因有多种

  • 另一个窗口遮挡了目标窗口的视图。
  • 窗口被调整大小、从最小化状态恢复或最大化。
  • 窗口被激活或停用,并且标题需要重新绘制。
  • 应用程序强制窗口重新绘制自己。

在任何时候,应用程序都可以使用此代码查询窗口的当前更新区域

//C: This code will get the entire update region.
HRGN hUpdateRgn;
hUpdateRgn = ::CreateRectRgn(0,0,0,0);

::GetUpdateRgn(hWnd, hUpdateRgn, FALSE);

//C: This code will simply retrieve the bounding box of the update region.
RECT rUpdateBox;
::GetUpdateRect(hWnd, &rUpdateBox, FALSE);

更新区域对于希望将其绘图代码剪裁到更新区域以提高性能的应用程序非常重要。

应用程序可以手动修改更新区域,通过向更新区域添加新部分(实际上使区域无效),或从更新区域中减去(或验证该区域)。以下是可用于修改更新区域的函数集。

InvalidateRect

InvalidateRect 函数向指定窗口的更新区域添加一个矩形。

BOOL InvalidateRect(
  HWND hWnd,           // handle to window
  CONST RECT *lpRect,  // rectangle coordinates
  BOOL bErase          // erase state
);

InvalidateRgn

InvalidateRgn 函数通过将指定区域内的客户区添加到窗口的当前更新区域来使其无效。

BOOL InvalidateRgn(
  HWND hWnd,    // handle to window
  HRGN hRgn,    // handle to region
  BOOL bErase   // erase state
);

ValidateRect

ValidateRect 函数通过从指定窗口的更新区域中移除矩形来验证矩形内的客户区。

BOOL ValidateRect(
  HWND hWnd,          // handle to window
  CONST RECT *lpRect  // validation rectangle coordinates
);

ValidateRgn

ValidateRgn 函数通过从指定窗口的当前更新区域中移除区域来验证区域内的客户区。

BOOL ValidateRgn(
  HWND hWnd,  // handle to window
  HRGN hRgn   // handle to region
);

RedrawWindow

RedrawWindow 函数更新窗口客户区中指定的矩形或区域。此函数将修改更新区域。此函数还可以根据设置的标志强制重新绘制窗口及其子窗口。

BOOL RedrawWindow(
  HWND hWnd,               // handle to window
  CONST RECT *lprcUpdate,  // update rectangle
  HRGN hrgnUpdate,         // handle to update region
  UINT flags               // array of redraw flags
);

以下是可用于此函数的标志列表以及每个标志功能的简短说明

  • RDW_ERASE:导致窗口在重新绘制时收到 WM_ERASEBKGND 消息。
  • RDW_FRAME:导致发送 WM_NCPAINT 消息。
  • RDW_INTERNALPAINT:即使更新区域为空,也导致将 WM_PAINT 消息发布到消息队列。
  • RDW_INVALIDATE:使用 lprcUpdatehrgnUpdate 使窗口无效。如果两者都为 NULL,则整个窗口无效。
  • RDW_NOERASE:抑制任何待处理的 WM_ERASEBKGND 消息。
  • RDW_NOFRAME:抑制任何待处理的 WM_NCPAINT 消息。
  • RDW_NOINTERNALPAINT:抑制任何不是更新区域结果的内部 WM_PAINT 消息。
  • RDW_VALIDATE:使用 lprcUpdatehrgnUpdate 验证窗口。如果两者都为 NULL,则整个窗口有效。
  • RDW_ERASENOW WM_ERASEBKGND WM_NCPAINT 消息将在函数退出前发送。
  • RDW_UPDATENOW:窗口将在函数退出前收到 WM_PAINT 消息。
  • RDW_ALLCHILDREN:在重绘操作中包括所有子窗口。
  • RDW_NOCHILDREN:在重绘操作中排除所有子窗口。

影响更新区域的类样式

窗口有两种类样式,当窗口调整大小时会影响窗口的更新区域。它们是 CS_HREDRAWCS_VREDRAW。这些标志可以以任何组合设置。如果窗口水平调整大小并且设置了 CS_HREDRAW 样式,则整个显示将被无效并重新绘制。同样,如果窗口垂直调整大小并且设置了 CS_VREDRAW 样式,则整个显示将被无效。这些样式对于视图可以滚动的显示很有用。

WM_PAINT / WM_NCPAINT 消息

窗口负责绘制自己的显示。窗口应响应 WM_PAINT 消息绘制其显示。如果需要更新非客户区,则绘图应发生在 WM_NCPAINT 中。 WM_PAINT WM_NCPAINT 消息仅在更新区域不为空时生成。还有其他方法可以强制生成 WM_PAINT 消息,如上所述,RedrawWindow 函数可以做到这一点。然而,由于更新区域的性质,直接发送 WM_PAINT 消息是不明智的。

当处理 WM_PAINT WM_NCPAINT 消息时,必须正确处理它们。最大的风险是更新区域的管理不当。如果该区域的部分过早验证,则窗口的某些部分将永远不会更新。另一方面,如果更新区域的部分从未验证,则系统将认为窗口始终需要更新,并将继续发送 WM_PAINT 消息。这将严重影响性能。

WM_ERASEBKGND 消息

WM_ERASEBKGND 消息的生成是为了在窗口绘制之前清除其客户区。WM_ERASEBKGND 消息在几个地方生成。此消息最常见的生成位置是在 WM_PAINT 处理程序调用 BeginPaint 中。这是在 WM_PAINT 处理程序内部调用 BeginPaint 的另一个重要原因。

WM_ERASEBKGND 消息在 wParam 参数中传递一个预初始化的 DC。处理 WM_ERASEBKGND 的应用程序应使用此 DC 绘制背景,因为执行的任何绘图操作都将剪裁到当前更新区域,以减少更新时的闪烁。

设备上下文

有四种类型的 DC。每个 DC 的基本目的是为设备绘图提供基础。可以绘制的区域由创建的 DC 类型决定。本节将描述绘图中使用的不同类型的 DC 以及它们如何被 MFC 和 WTL 封装。

将要描述的第一种 DC 类型是通用 DC。通用 DC 封装了两种类型的 DC:窗口 DC 和客户 DC。然后第二部分将介绍设备 DC。这些 DC 表示您机器上的任何设备,它支持某种形式的 Windows 图形设备接口 (GDI)。本文忽略了另外两种 DC 类型:内存 DC 和图元文件 DC。

DC 是一种操作系统资源。正因为如此,当不再需要 DC 时,释放它们非常重要。不要缓存 DC。每种类型的 DC 都有自己的方法来释放该 DC。使用正确的函数很重要,因为这些函数的作用类似于析构函数,并且这些函数中会发生特定类型 DC 的重要关闭操作。如果应用程序每次初始化 DC 都执行耗时操作,则可以创建类或私有 DC。这将在本节末尾解释。

通用 DC

通用 DC 是从 DC 池中创建的,该池受当前系统内存量的限制。通用 DC 进一步分类为客户端 DC 和窗口 DC。客户端 DC 和窗口 DC 非常相似。两者之间唯一的区别是客户端 DC 仅限于窗口的客户端区域,而窗口 DC 允许应用程序访问窗口的客户端和非客户端区域。有两个函数允许访问客户端 DC(BeginPaintGetDC,),一个函数用于创建窗口 DC(GetWindowDC),还有一个主函数可以为窗口创建任何类型的 DC(GetDCEx)。

下面列出了可用于为窗口创建 DC 的每个函数。描述了每个函数的特殊特性以及特殊用途。

绘图 DC

绘图 DC 在 MFC 和 WTL 中由 CPaintDC 类表示。此类是 BeginPaint 函数的包装器,该函数准备指定的窗口进行绘图并使用有关绘图的信息初始化 PAINTSTRUCT 结构。

HDC BeginPaint(
  HWND hwnd,            // handle to window
  LPPAINTSTRUCT lpPaint // paint information
);

BeginPaint 通过创建具有与窗口更新区域等效的剪辑区域的客户端 DC 来准备窗口进行绘图。然后验证更新区域以防止生成其他 WM_PAINT 消息。剪辑区域在重新绘制时设置到客户端 DC 中,因为当整个显示被重新绘制时,这具有大大减少闪烁的效果。然而,这种形式的剪辑不会消除所有形式的闪烁,特别是对于需要很长时间才能重新绘制的复杂显示。

以下是 PAINTSTRUCT 结构的布局

struct PAINTSTRUCT { 
  HDC  hdc; 
  BOOL fErase; 
  RECT rcPaint; 
  BOOL fRestore; 
  BOOL fIncUpdate; 
  BYTE rgbReserved[32]; 
};

以下是每个字段的描述

  • hdc:在 BeginPaint 中创建的 DC 的句柄。这与 BeginPaint 返回的句柄相同。
  • fErase:指示应用程序是否负责擦除绘图显示的背景。如果对 WM_ERASEBKGND 的调用成功,则此参数通常为 FALSE
  • rcPaint:指定一个 RECT 结构,该结构指定请求绘图的矩形的左上角和右下角。这基本上是窗口更新区域的边界矩形。
  • fRestore:保留;系统内部使用。
  • fIncUpdate:保留;系统内部使用。
  • rgbReserved:保留;系统内部使用。

BeginPaint 在准备窗口绘制时执行其他一些操作。如果当前窗口包含插入符,它将隐藏插入符,以防止窗口在其上方绘制。它将发送一条消息到 WM_NCPAINT 以在必要时更新边框。在 DC 已使用当前剪辑区域初始化后,将发送一条消息到 WM_ERASEBKGND WM_ERASEBKGND 将仅擦除当前更新区域。

CPaintDC 类通过调用 EndPaint 正确释放 DC。EndPaint 函数标记指定窗口中绘图的结束。此函数是每次调用 BeginPaint 函数所必需的,但仅在绘图完成后。EndPaint 做的另一件事是将之前在调用 BeginPaint 中隐藏的插入符恢复到屏幕。

BOOL EndPaint(
  HWND hWnd,                  // handle to window
  CONST PAINTSTRUCT *lpPaint  // paint data
);

BeginPaint 应该只在 WM_PAINT 处理程序内部调用。这是因为更新区域。如果在 WM_PAINT 处理程序之外需要 DC,则应改用 GetDC

客户端 DC

在 MFC 和 WTL 中,用于绘制客户端区域但不是绘图 DC 的 DC 称为 ClientDC。此 DC 由 CClientDC 类表示。此类封装了对 GetDC 的调用。除了 OnPaint 处理程序之外,需要绘制到窗口客户端区域的任何地方都应使用此类型的 DC。如果将 NULL 作为窗口句柄传入,GetDC 还可以获取一个 DC 以在整个屏幕上绘制。

HDC GetDC(
  HWND hWnd   // handle to window
);

使用通用 DC 绘图后,必须调用 ReleaseDC 函数来释放 DC。类 DC 和私有 DC 不需要释放。 ReleaseDC 必须从调用 GetDC 的同一线程中调用。 ReleaseDC 函数释放设备上下文 (DC),使其可供其他应用程序使用。 ReleaseDC 函数的效果取决于 DC 的类型。它仅释放通用 DC 和窗口 DC。它对类 DC 或私有 DC 没有影响。

int ReleaseDC(
  HWND hWnd,  // handle to window that the DC was created for.
  HDC hDC     // handle to DC
);

窗口 DC

窗口 DC 允许绘制整个窗口,包括标题栏、菜单和滚动条。窗口设备上下文允许在窗口的任何位置绘制,因为设备上下文的原点是窗口的左上角,而不是客户区。MFC 和 WTL 用 CWindowDC 类表示此 DC,该类内部调用 GetWindowDC 函数来创建 DC。如果将 NULL 作为窗口句柄传入,GetWindowDC 还可以获取一个 DC 以在整个屏幕上绘制。

HDC GetWindowDC(
  HWND hWnd   // handle to window
);

GetDC 一样,GetWindowDC 应该通过调用 ReleaseDC 释放,CWindowDC 会自动处理。

GetDCEx

GetDCEx 是允许开发人员创建与窗口任何部分关联的 DC 的函数。前面列出的三个函数都是通过调用 GetDCEx 实现的。如果将 NULL 作为窗口句柄传入,GetDCEx 还可以获取一个 DC 以在整个屏幕上绘制。

HDC GetDCEx(
  HWND hWnd,      // handle to window
  HRGN hrgnClip,  // handle to clipping region
  DWORD flags     // creation options
);

flags 参数将决定创建哪种类型的 DC 以及如何初始化 DC。以下是可用于使用 GetDCEx 创建 DC 的不同标志的简短列表

  • DCX_WINDOW:返回与窗口矩形而不是客户矩形对应的 DC。
  • DCX_CACHE:从缓存返回 DC,而不是 OWNDC CLASSDC 窗口。实质上覆盖 CS_OWNDC 和 CS_CLASSDC。
  • DCX_PARENTCLIP:使用父窗口的可见区域。忽略父窗口的 WS_CLIPCHILDREN CS_PARENTDC 样式位。原点设置为由 hWnd 标识的窗口的左上角。
  • DCX_CLIPSIBLINGS:排除由 hWnd 标识的窗口上方所有同级窗口的可见区域。
  • DCX_CLIPCHILDREN:排除由 hWnd 标识的窗口下方所有子窗口的可见区域。
  • DCX_NORESETATTRS:释放此 DC 时,不将此 DC 的属性重置为默认属性。
  • DCX_LOCKWINDOWUPDATE:即使 LockWindowUpdate 调用生效,否则将排除此窗口,也允许绘图。用于跟踪期间的绘图。
  • DCX_EXCLUDERGN:由 hrgnClip 标识的剪辑区域从返回的 DC 的可见区域中排除。
  • DCX_EXCLUDEUPDATE:行为与 DCX_EXCLUDERGN 相同,只是使用窗口的更新区域作为输入,而不是 hrgnClip 参数。
  • DCX_INTERSECTRGN:由 hrgnClip 标识的剪辑区域与返回的 DC 的可见区域相交。
  • DCX_INTERSECTUPDATE:行为与 DCX_INTERSECTRGN 相同,只是使用窗口的更新区域作为输入,而不是 hrgnClip 参数。
  • DCX_VALIDATE:与 DCX_INTERSECTUPDATE 一起指定时,导致 DC 完全验证。将此函数与 DCX_INTERSECTUPDATE DCX_VALIDATE 一起使用与使用 BeginPaint 函数相同。

如前所述,BeginPaint、GetDC、GetWindowDC 都通过 GetDCEx 实现。下面列出了这些标志的表格,以及为了创建等效 DC 而应向 GetDCEx 发出的正确调用。在所有示例中,hWnd 是目标窗口的句柄。

  • BeginPaint
    ::GetDCEx(hWnd, NULL, DCX_INTERSECTUPDATE | 
                          DCX_VALIDATE | /* Other flags depending on class styles*/);
  • GetDC
    ::GetDCEx(hWnd, NULL, NULL);
  • GetWindowDC
    ::GetDCEx(hWnd, NULL, DCX_WIDNOW);

类/私有 DC

在应用程序中不要缓存通用 DC 很重要。那么应用程序如何创建一个每次调用都不需要重新初始化的 DC 呢?这可以通过类 DC 或私有 DC 来完成。这两种 DC 可以以与其他通用 DC 相同的方式检索,并且它们不需要释放。然而,释放这些 DC 是一个好习惯,因为释放函数没有效果,并且如果 DC 曾经被转换回通用 DC,则可以防止内存泄漏。

类 DC 和私有 DC 是持久的,因此 DC 的状态将与上次调用该 DC 时保持相同。这将允许 DC 只初始化一次。这可以为具有冗长 DC 初始化的应用程序节省宝贵的时间。

下面是类 DC 和私有 DC 的简短描述。

  • 类 DC

    类 DC 是为单个窗口类创建的。为了创建类 DC,窗口类必须设置 CS_CLASSDC 样式。由于可以在不同线程上创建相同类的两个窗口,因此同步对类 DC 的访问很重要。在两个不同线程上同时使用类 DC 会导致未定义的行为。

  • 私有 DC

    私有 DC 是为特定窗口创建的。要创建私有 DC,必须在窗口类中设置 CS_OWNDC 样式。

设备 DC

设备 DC 允许访问系统中支持 WIN32 GDI 某些部分的任何设备。可以访问所有类型的设备,例如“DISPLAY”驱动程序、辅助显示器、打印机、绘图仪以及任何其他支持 GDI 的尖端设备。

获取设备 DC 句柄需要两个步骤。

  1. 获取设备名称。
  2. 调用 CreateDC 为设备创建 HDC。

当应用程序完成 DC 的使用后,应使用 DeleteDC 销毁 HDC。

获取设备名称

最简单地,如果应用程序想要主显示设备的 DC,则可以使用 string "DISPLAY"。但是,如果要为辅助显示器创建 DC,则必须使用 EnumDisplayMonitors 来获取目标显示驱动程序的名称,因为 WIN95 不支持多显示器,此函数仅在 WIN98 及更高版本上有效。

为了获取打印机设备的名称,应用程序应使用 EnumPrinters。此函数将允许应用程序搜索系统中配置的所有打印机。还可以从打印机查询重要信息,以便在 CreateDC 中为其创建 DC。

CreateDC

CreateDC 函数使用指定的名称为设备创建 DC。

HDC CreateDC(
  LPCTSTR lpszDriver,        // driver name
  LPCTSTR lpszDevice,        // device name
  LPCTSTR lpszOutput,        // not used; should be NULL
  CONST DEVMODE *lpInitData  // optional printer data
);

lpInitDataDEVMODE 结构主要对打印机设备上下文非常重要。然而,此结构也非常复杂,因为本文不涉及打印,所以关于 DEVMODE 的所有内容就到此为止。

CreateIC

CreateIC 函数为指定设备创建信息上下文。信息上下文提供了一种快速获取设备信息的方法,而无需创建设备上下文 (DC)。但是,GDI 绘图函数不能接受信息上下文的句柄。当不再需要信息上下文时,此函数应后跟对 DeleteDC 的调用。

HDC CreateIC(
  LPCTSTR lpszDriver,       // driver name
  LPCTSTR lpszDevice,       // device name
  LPCTSTR lpszOutput,       // port or file name
  CONST DEVMODE *lpdvmInit  // optional initialization data
);

演示

为演示本文涵盖的主题而创建的程序将为用户可视化更新区域。其他信息也将显示在主窗口的非客户端区域中,或直接显示在其中一个显示驱动程序上。演示应用程序的另一个功能是关闭 WM_ERASEBKGND 消息的处理,以查看产生什么效果。

由于多显示器测试,此应用程序需要 WIN98、WIN200 或更高版本。此应用程序使用 WTL 编写,因此构建源项目将需要 WTL 头文件。还将使用 WTL 包装类(CPaintDCCWindowDC 等)来说明每种不同 DC 的使用。此应用程序还需要 5.0 版的头文件和 lib 文件才能编译,因为它使用了仅在 WIN98 和 WIN2000 或更高版本中支持的 EnumDisplayDevices 函数。

此应用程序的使用非常简单。运行应用程序,可以强制主窗口重新绘制自己。有很多方法可以做到这一点,包括

  • 最大化/恢复主窗口
  • 水平/垂直/对角线调整窗口大小
  • 拖动某个对象穿过窗口以使新区域失效
  • 将窗口置于窗口中心,然后激活演示应用程序

应查看所有这些使窗口无效的方法。然后,应执行以下每个步骤以查看系统中的某些设置如何改变事物。

  • 在视图菜单中修改 CS_HREDRAW / CS_VREDRAW 样式
  • 修改 WM_ERASEBKGND 消息的处理
  • 更改视图菜单上的“拖动时显示窗口内容”选项。

每次收到 WM_PAINT 区域时,该窗口的当前更新区域将用新颜色框定。这将允许用户在每个新的 WM_PAINT 消息发生时可视化更新区域。窗口可以设置一些样式标志,这些标志将演示它们对 WM_PAINT 消息的影响。以下是一些可视化更新区域的屏幕图像

Update region created by resizing the window.

Update region created by dragging another application window across the window.

数据日志

数据日志包含 PAINTSTRUCT 结构的重要且可用的值。这是数据条处于活动状态时的图像。

以下是每个参数将演示的描述

  • HDC:在 WM_PAINT 中分配的 HDC 的值。显示为每条消息创建一个新的 HDC
  • fErase:这显示了 WM_PAINT 处理程序是否负责擦除背景。如果在菜单中设置了 WM_ERASEBKGND,则该消息将得到正确处理,并且 fErase 将在数据条上显示为 FALSE。如果未处理 WM_ERASEBKGND,则 fErase 将为 TRUE。视图窗口从不处理 WM_ERASEBKGND,因此您会看到一个奇怪的效果,它看起来像窗口没有正确更新,除了更新区域将正确框定。
  • rcPaint:此矩形的四个组件将显示,演示窗口上更新区域的边界矩形。

在菜单上选择无日志将隐藏数据条。

如果菜单中选择了非客户端,则数据条将绘制在窗口的边框区域中。这样做是为了说明如何在 WM_NCPAINT 消息处理程序中使用 CWindowDC 进行绘图。

此程序将枚举您的机器上最多两个显示设备,并将它们添加到视图菜单。如果您选择其中一个项目,则数据条将显示在该显示器所代表的监视器左上角。CreateDC 函数用于使用该监视器的设备名称创建 DC。添加此功能是为了演示获取任何显示器的 DC。

CS_HREDRAW / CS_VREDRAW 样式

如本文前面所述,这些样式会影响更新区域。如果设置了其中一种样式,那么当窗口调整大小时,相应的样式可能会强制重新绘制整个视图,而不仅仅是新的更新区域。更改此样式,并观察它对更新区域的影响。

WM_ERASEBKGND

如果此选项设置为,则应用程序将处理 WM_ERASEBKGND 消息,在每次 WM_PAINT 消息期间有效地清除背景。但是,如果此项未选中,则应用程序将忽略 WM_ERASEBKGND 消息。这将导致在窗口顶部留下任何使区域无效的内容,而不是刷新显示。

另请注意数据条中的 fErase 字段值。当处理 WM_ERASEBKGND 时, fEraseFALSE,表示 WM_PAINT 例程不负责初始化绘图表面。如果未处理 WM_ERASEBKGND,则 PAINTSTRUCT 中的 fErase 将为 true,通知应用程序在绘图之前应擦除显示背景,而此应用程序为了演示此过程并未执行此操作。

拖动时显示窗口内容

这将切换显示属性中的设置,该设置强制窗口在拖动时重新绘制。无论是调整大小还是另一个窗口在窗口顶部拖动,此选项设置的最终效果是会生成更多绘图消息,并且更新区域会更小但更频繁。应在此选项打开和关闭的情况下查看此应用程序。

这是调整窗口大小时差异的一个示例。

Update region created when full drag mode is off.  Update region created when full drag mode is on.

缺点

当数据条绘制在其中一个显示适配器上时,此演示应用程序存在两个小缺点。当数据条被移除时,应用程序不会尝试擦除或恢复之前的数据。这可以通过缓存数据条被绘制的区域并在数据条被移除时恢复该区域来解决。但是,那将需要内存 DC,而本文忽略了它们的解释。

当数据条显示在其中一个显示适配器上,但它也绘制到另一个应用程序的窗口上时,数据条可能看起来错过了它应该绘制的区域。这实际上是由另一个应用程序在数据条绘制在其上方之后收到其 WM_PAINT 消息引起的。这可以通过找到在数据条上方绘制的窗口并验证该区域,或通过窗口钩子来解决。但是,此选项尚未探索。

结论

WIN32 绘图架构旨在逐步绘制窗口和控件。这提高了应用程序的性能和外观。增量绘图过程由 WIN32 为每个窗口维护的更新区域启用。 WM_PAINT 消息不是由系统推送到消息队列的。相反, WM_PAINT 消息仅在处理完队列中的所有其他消息并且窗口的更新区域不为空时才生成。 WM_PAINT 消息应使用 BeginPaint API 处理,如果使用 MFC 或 WTL,则使用 CPaintDC

还有其他类型的通用 DC 可用于在窗口或其中一个显示器上绘图。每种类型的 DC 都有一个特殊用途。客户端 DC 用于在窗口的客户端区域上绘图。使用 GetDC API 或 CClientDC 类。窗口 DC 允许应用程序绘制整个窗口。使用 GetWindowDC API 或 CWindowDC 类。然后,GetDCEx 是所有通用 DC 函数的根 API 函数。每个其他通用 DC 都可以通过调用 GetDCEx 和适当的标志集来创建。如果应用程序需要直接在桌面绘图,则可以使用 CreateDC 创建用于此目的的 DC。

在 WIN32 中有许多不同的方法来绘制窗口。本教程介绍了 WIN32 中发现的设计、结构和过程的许多事实。希望读者能够利用这些事实并以提高其应用程序性能和外观的方式应用它们。

© . All rights reserved.