BeginPaint/EndPaint(CPaintDC)与GetDC(CClientDC)的区别

本文探讨了在OnPaint函数中使用CClientDC与CPaintDC的区别。详细解释了为什么使用CClientDC会导致界面闪烁,并提供了避免闪烁的技术建议。

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

在OnPaint函数中,用CClientDC dc(this)代替CPaintDC(this)后,界面不断闪烁。

说明:CClientDC是对GetDC的使用封装, CPaintDC是对BeginPaint/EndPaint的使用封装

 

原因:BeginPaint/EndPaint会将无效区域设置为NULL, 从而通知系统已经绘制了,不用再发WM_PAINT消息,

而GetDC不会这样做,若在OnPaint函数中直接调用GetDC来获得设备进行绘制,不做其它操作,系统会不断的

发送WM_PAINT消息,从而导致不断重绘而闪烁。

 

以下摘自MSDN,

BeginPaint sets the update region of a window to NULL. This clears the region, preventing it from generating subsequent WM_PAINT messages. If an application processes a WM_PAINT message but does not call BeginPaint or otherwise clear the update region, the application continues to receive WM_PAINT messages as long as the region is not empty. In all cases, an application must clear the update region before returning from the WM_PAINT message.

After the application finishes drawing, it should call EndPaint. For most windows, EndPaint releases the display device context, making it available to other windows. EndPaint also shows the caret, if it was previously hidden by BeginPaint. BeginPaint hides the caret to prevent drawing operations from corrupting it. 

转载于:https://www.cnblogs.com/shanql/p/6574388.html

<think>我们讨论的是在同一个窗口过程中混合使用Direct2DGDI(BeginPaint/EndPaint)的情况。关键在于理解BeginPaint/EndPaintValidateRect的机制。 核心点: 1. BeginPaint函数会获取窗口的无效区域(通过PAINTSTRUCT.rcPaint),并且会验证整个窗口(相当于ValidateRect(hwnd, &ps.rcPaint)),但注意:它只验证当前绘制操作覆盖的区域(即无效区域),而是整个窗口。 2. 如果你在WM_PAINT消息处理中使用了BeginPaint,那么无论你绘制的是局部还是全局,在调用EndPaint时,系统会自动验证(标记为已更新)在BeginPaint调用时获取的无效区域(即PAINTSTRUCT中的rcPaint区域)。 3. 但是,如果你在同一个WM_PAINT消息处理中既使用了BeginPaint又使用了Direct2D绘制,那么需要注意: - 如果你先调用BeginPaint,它会获取当前无效区域,然后你使用Direct2D绘制,最后调用EndPaint会验证这个无效区域。 - 但是,如果你在调用BeginPaint之后又调用了InvalidateRect或者手动修改了无效区域,那么情况会变得复杂。 问题:使用Direct2D绘制了局部区域,同时使用了BeginPaint/EndPaint,那么更新的是局部还是全局? 答案:这取决于你如何使用BeginPaintEndPaint,以及你绘制的区域是否覆盖了无效区域。 详细解释: 假设我们有一个窗口,其中只有一小块区域是无效的(比如被另一个窗口遮挡后重新显示)。系统会发送WM_PAINT消息,并且在BeginPaint调用中,PAINTSTRUCT的rcPaint字段就是那个无效区域。 如果你在BeginPaintEndPaint之间使用Direct2D绘制了整个窗口(包括无效区域非无效区域),那么实际上你绘制的内容覆盖了无效区域。当EndPaint被调用时,它会验证(ValidateRect)这个无效区域(rcPaint),这样系统就会再发送WM_PAINT消息了,直到有新的无效区域产生。 但是,如果你只绘制了局部区域(比如只绘制了无效区域的一部分),那么没有绘制的部分仍然保持无效,系统会继续发送WM_PAINT消息,直到整个无效区域被绘制并验证。 然而,Direct2D本身并BeginPaint/EndPaint的验证机制直接关联。BeginPaint/EndPaint是GDI的机制,它通过获取设备上下文(HDC验证区域来工作。而Direct2D通常使用自己的渲染目标(ID2D1HwndRenderTarget)进行绘制,并依赖于BeginPaint返回的HDC(除非你使用D2D1CreateDCRenderTarget创建了DC渲染目标)。 所以,如果你在WM_PAINT消息中同时使用GDI(通过BeginPaintDirect2D(通过HwndRenderTarget)进行绘制,那么你需要注意: 1. 如果你使用BeginPaint,那么你获得了HDC,并且这个HDC对应的区域是无效区域。然后你使用这个HDC进行GDI绘制,最后EndPaint会验证这个区域。 2. 同时,如果你使用Direct2D的HwndRenderTarget进行绘制,它并影响这个验证过程,因为HwndRenderTarget的绘制是独立于这个HDC的。 因此,如果你在同一个WM_PAINT消息中既使用BeginPaint/EndPaint又使用Direct2D绘制,那么你实际上做了两件事: - 通过GDI绘制(可能只是部分或者什么都做)并验证了无效区域(通过EndPaint)。 - 通过Direct2D绘制了内容(可能是整个窗口,也可能是局部),但Direct2D的绘制会改变窗口的无效区域状态。 这样可能导致的问题是:如果Direct2D绘制的内容并没有覆盖整个无效区域,但由于EndPaint已经验证了该区域,那么未覆盖的部分就会再被绘制,从而出现显示残留。 所以,通常的做法是: 方案1(推荐):完全使用Direct2D,使用BeginPaint/EndPaint。在WM_PAINT中,使用BeginPaint,而是直接使用Direct2D的渲染目标进行绘制,然后调用ValidateRect(hwnd, nullptr)来验证整个窗口(或者用GetUpdateRect获取无效区域,然后只绘制该区域,最后验证该区域)。 方案2:混合使用,但要注意: - 如果你使用BeginPaint,那么你必须在BeginPaintEndPaint之间使用GDI绘制整个无效区域,否则未绘制的部分将变成黑色(或者背景刷)并且被验证掉。 - 同时,如果你还想用Direct2D绘制,那么你可以将Direct2D绘制到内存位图,然后使用GDI将位图复制到BeginPaint返回的HDC上(这样就能保证在同一个绘制操作中覆盖整个无效区域)。 但是,更常见的做法是方案1:避免混合使用。 示例代码(纯Direct2D处理WM_PAINT): ```cpp case WM_PAINT: { // 使用BeginPaint,因为我们打算使用GDI PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 注意:这里调用BeginPaint只是为了获取无效区域,但通常我们需要它,因为Direct2D有自己的方法 EndPaint(hwnd, &ps); // 立即结束绘制,这样我们就会阻塞,但这样也会验证无效区域,所以这是我们想要的! // 所以,上面的做法是错误的,因为BeginPaint/EndPaint会验证区域,而我们还没有用Direct2D绘制。 // 正确做法:使用BeginPaint/EndPaint,而是用以下步骤: // 步骤1:获取无效区域(可选,用于局部更新优化) RECT invalidRect; if (GetUpdateRect(hwnd, &invalidRect, FALSE)) { // 存在无效区域 // 我们可以只重绘这个区域,但通常Direct2D应用会重绘整个窗口(因为硬件加速很快) } else { // 没有无效区域?通常会发生,因为WM_PAINT只在有无效区域时产生 // 但为了安全,可以返回 return 0; } // 步骤2:使用Direct2D渲染 pRenderTarget->BeginDraw(); // 清除背景(如果需要,可以清除整个窗口,或者只清除无效区域) pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); // 绘制内容(可以只绘制局部,但通常简单应用会绘制全部) // ... 绘制操作 ... HRESULT hr = pRenderTarget->EndDraw(); // 步骤3:处理设备丢失 if (hr == D2DERR_RECREATE_TARGET) { // 重新创建资源 DiscardDeviceResources(); CreateDeviceResources(); // 重新创建渲染目标等 // 然后重新绘制?或者等待下一次WM_PAINT(我们这里标记无效区域,触发重绘) InvalidateRect(hwnd, nullptr, TRUE); } else { // 步骤4:验证整个窗口(或无效区域)以阻止WM_PAINT继续产生 ValidateRect(hwnd, nullptr); // 验证整个窗口 // 或者,如果你只想验证无效区域: // ValidateRect(hwnd, &invalidRect); } return 0; } ``` 注意:上面的代码中,我们通过GetUpdateRect获取无效区域,然后进行绘制,最后验证。但是,在Direct2D中,我们通常使用整个窗口的渲染目标,所以即使只有局部无效,我们也可以选择重绘整个窗口(因为硬件加速很快,而且可以避免复杂的部分绘制逻辑)。然后验证整个窗口。 所以,回到问题:如果你在同一个WM_PAINT消息中使用了BeginPaint/EndPaint,那么EndPaint会验证当前无效区域(无论你绘制了什么)。而Direct2D的绘制是独立进行的,会影响这个验证过程。因此,如果你在EndPaint之后用Direct2D绘制,那么当EndPaint验证后,系统认为窗口已经更新,但Direct2D绘制的内容可能会被覆盖(因为GDI的绘制在Direct2D之前)?实际上,这取决于Direct2D渲染目标是如何创建的。 如果是HwndRenderTarget,它直接绘制到窗口,所以它GDI绘制到同一个窗口表面。但谁后绘制,谁就显示在上面。所以如果你先调用BeginPaint(它可能清除背景,取决于窗口类背景刷),然后进行GDI绘制,然后EndPaint(验证区域),然后再用Direct2D绘制,那么Direct2D的内容会覆盖在GDI绘制的内容之上。 但是,由于验证已经完成,即使Direct2D绘制的内容没有覆盖整个无效区域,系统也会再发送WM_PAINT,所以未覆盖的部分将保持上一次绘制的内容(可能是GDI绘制的内容,或者是残留内容)。 因此,混合使用容易导致问题,建议避免。 总结: - 使用纯Direct2D:在WM_PAINT中使用BeginPaint/EndPaint,而是用GetUpdateRect(可选)ValidateRect。 - 如果你使用了BeginPaint/EndPaint,那么你必须在它们之间完成所有绘制(包括Direct2D,如果通过GDI位图复制的方式),并且要确保覆盖整个无效区域。 所以,回答你的问题:如果你用Direct2D绘制了局部区域,同时使用了BeginPaint/EndPaint,那么EndPaint会验证整个无效区域(即调用BeginPaint时ps.rcPaint的区域),而管Direct2D绘制了哪里。因此,如果Direct2D绘制的区域小于无效区域,那么未绘制的部分将显示为背景刷(如果你在窗口类中设置了背景刷)或者黑色(如果没有设置背景刷),然后被验证掉。这样就会导致显示完整。 因此,推荐的做法是:要混合使用。要么全部用GDI(BeginPaint/EndPaint),要么全部用Direct2D(手动验证)。 相关问题: §§相关问题§§: 1. 在同一个窗口过程中混合使用GDIDirect2D进行绘制有哪些风险? 2. 如何正确使用Direct2D处理窗口的WM_PAINT消息? 3. 当窗口有多个无效区域时,Direct2D如何优化只重绘部分区域? 4. 使用Direct2D时,如何处理窗口的背景擦除(WM_ERASEBKGND消息)?</think>### Direct2D 局部绘制 `BeginPaint/EndPaint` 的更新范围解析 #### 核心结论 **局部更新**还是**全局更新**取决于您的实现方式: - **使用 `BeginPaint/EndPaint` 时**:默认更新**局部无效区域** - **使用 `ValidateRect(nullptr)` 时**:强制标记**全局为已更新** - **Direct2D 本身**:可自由选择局部或全局更新 #### 关键机制图解 ```mermaid graph TD A[WM_PAINT 触发] --> B{使用哪种绘制方式?} B -->|BeginPaint/EndPaint| C[自动获取无效区域] B -->|Direct2D + ValidateRect| D[手动控制更新范围] C --> E[仅重绘 rcPaint 区域] D --> F[可自由选择全局或局部] ``` #### 局部更新实现(推荐) ```cpp // 示例:使用 Direct2D 仅更新无效区域 case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 获取无效区域信息 // 获取精确无效区域 RECT invalidRect = ps.rcPaint; if (pRenderTarget) { pRenderTarget->BeginDraw(); // 1. 清除背景(仅清除无效区域) pRenderTarget->PushAxisAlignedClip( D2D1::RectF(invalidRect.left, invalidRect.top, invalidRect.right, invalidRect.bottom), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE ); pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); pRenderTarget->PopAxisAlignedClip(); // 2. 仅绘制无效区域内的内容 if (RectInInvalidArea(invalidRect)) { RenderPartialContent(invalidRect); // 自定义局部渲染函数 } HRESULT hr = pRenderTarget->EndDraw(); // 3. 仅验证已更新的区域 ValidateRect(hwnd, &invalidRect); } EndPaint(hwnd, &ps); // 自动验证无效区域 return 0; } ``` #### 全局更新实现 ```cpp // 示例:强制全局更新 case WM_PAINT: { // 跳过传统 GDI 获取 // PAINTSTRUCT ps; // BeginPaint(hwnd, &ps); if (pRenderTarget) { pRenderTarget->BeginDraw(); // 1. 清除整个背景 pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); // 2. 绘制全部内容 RenderFullContent(); // 自定义全局渲染函数 HRESULT hr = pRenderTarget->EndDraw(); // 3. 标记整个窗口为已更新 ValidateRect(hwnd, nullptr); } // 需要 EndPaint return 0; } ``` #### 关键差异对比表 | 特性 | `BeginPaint/EndPaint` | `ValidateRect(nullptr)` | |------|------------------------|--------------------------| | 更新范围 | 自动限制为无效区域 | 强制全局更新 | | 获取无效区域 | 通过 `PAINTSTRUCT.rcPaint` | 需手动调用 `GetUpdateRect` | | 验证机制 | 自动验证 | 手动验证 | | GDI 兼容性 | 高 | 低 | | 性能优化 | 自动局部更新 | 需手动实现 | | Direct2D 集成 | 需额外处理 | 原生支持 | | 多显示器支持 | 有限 | 更好 | #### 混合使用注意事项 ```cpp // 危险示例:混合使用导致问题 case WM_PAINT: { PAINTSTRUCT ps; BeginPaint(hwnd, &ps); // 获取局部无效区域 // Direct2D 全局绘制 pRenderTarget->BeginDraw(); pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); RenderFullContent(); pRenderTarget->EndDraw(); ValidateRect(hwnd, nullptr); // 错误:全局验证 EndPaint(hwnd, &ps); // 错误:再次验证局部区域 // 结果:可能导致绘制闪烁或更新异常 return 0; } ``` #### 最佳实践建议 1. **纯 Direct2D 方案**: ```cpp // 推荐:完全跳过 GDI 机制 case WM_PAINT: if (GetUpdateRect(hwnd, nullptr, FALSE)) { // 检查是否有无效区域 RenderDirect2DContent(); ValidateRect(hwnd, nullptr); // 或局部验证 } return 0; ``` 2. **高性能局部更新**: ```cpp void RenderDirect2DContent() { RECT invalidRect; GetUpdateRect(hwnd, &invalidRect, FALSE); pRenderTarget->BeginDraw(); // 设置裁剪区域 pRenderTarget->PushAxisAlignedClip( D2D1::RectF(invalidRect.left, invalidRect.top, invalidRect.right, invalidRect.bottom), D2D1_ANTIALIAS_MODE_ALIASED ); // 智能渲染:只更新受影响的内容 for (auto& obj : sceneObjects) { if (obj.Intersects(invalidRect)) { obj.Render(pRenderTarget); } } pRenderTarget->PopAxisAlignedClip(); pRenderTarget->EndDraw(); } ``` 3. **动画场景优化**: ```cpp void RequestAnimationFrame() { // 标记特定区域为无效 RECT animationArea = CalculateMovingArea(); InvalidateRect(hwnd, &animationArea, FALSE); // 下一帧请求 SetTimer(hwnd, ANIMATION_TIMER, 16, nullptr); // ≈60fps } ``` ### 结论:更新范围由您掌控 > **关键原则**: > 1. `BeginPaint/EndPaint` 默认更新**局部**(`rcPaint`区域) > 2. `ValidateRect(nullptr)` 强制标记**全局**更新 > 3. Direct2D 可通过裁剪实现**精确局部更新** > > **推荐方案**: > - 静态内容:`BeginPaint/EndPaint` + 局部 Direct2D 渲染 > - 动态内容:直接使用 `InvalidateRect` + `ValidateRect` 控制 > - 全屏动画:`ValidateRect(nullptr)` 全局更新 --- ### 相关问题: 1. 如何精确获取窗口的多个连续无效区域? 2. 在 Direct2D 中实现局部更新时如何优化渲染性能? 3. `BeginPaint` `GetDC` 在无效区域处理上有何本质区别? 4. 如何为 Direct2D 实现脏矩形(Dirty Rectangles)优化算法?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值