Visual Studio 2017 MFC SDI程序Direct2D绘图锁屏后客户区冻结问题
这大概率不是系统BUG,而是你遗漏了Direct2D资源在系统状态变化后的重置逻辑——尤其是锁屏/恢复这种场景下,Direct2D的渲染目标(Render Target)很容易失效,而你的代码没做有效性检查和重建。
为什么会出现这个问题?
锁屏时,系统会重置或重新创建与显示相关的底层资源,比如硬件加速的渲染上下文。你的Direct2D Render Target是绑定到窗口初始状态的DC上的,锁屏恢复后这个Render Target已经失效了,但你的绘制逻辑还在往旧的失效资源上输出,所以虽然断点触发了(代码在执行),但画面根本不会更新到屏幕上。
至于“只有鼠标在客户区锁屏才会出问题”,这是因为锁屏时窗口的激活状态/DC关联状态和鼠标在外部时不一样:当鼠标在客户区,窗口可能处于激活状态,系统没有自动重置窗口的相关资源;而鼠标在外部时窗口是非激活状态,系统恢复时自动帮你处理了资源重置,所以没出问题。本质还是资源有效性的问题。
验证方法
你可以在OnDraw2d的开头加一段代码,检查Render Target的状态:
HRESULT hr = m_pRenderTarget->CheckState(); if (FAILED(hr)) { // 这里可以输出日志或者断点,看看是不是D2DERR_RECREATE_TARGET错误 TRACE(_T("Render Target失效,错误码:%x\n"), hr); }
如果锁屏后触发了D2DERR_RECREATE_TARGET,那就坐实了资源失效的问题。
解决方案
1. 在绘制前强制检查并重建Render Target
修改你的OnDraw2d方法,每次绘制前先确认Render Target有效,失效就重建:
void CIGUIDEView::OnDraw2d(WPARAM wParam, LPARAM lParam) { HRESULT hr = S_OK; // 检查Render Target状态 if (m_pRenderTarget) { hr = m_pRenderTarget->CheckState(); if (hr == D2DERR_RECREATE_TARGET) { // 释放旧的Direct2D资源 SafeRelease(&m_pRenderTarget); // 这里替换成你初始化Render Target的代码 CreateRenderTarget(); } } // 确保Render Target有效后再执行绘制 if (m_pRenderTarget) { m_pRenderTarget->BeginDraw(); // 你的绘制逻辑... hr = m_pRenderTarget->EndDraw(); } }
注意:SafeRelease是MFC常用的资源释放宏,如果你没有定义,可以自己写一个:
template<class T> void SafeRelease(T **ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } }
2. 监听系统显示变化消息,提前重置资源
除了绘制时检查,还可以监听WM_DISPLAYCHANGE或WM_WINDOWPOSCHANGED消息,在系统显示状态变化(比如锁屏恢复、分辨率调整)时主动重置资源:
在你的View类的消息映射里添加:
ON_WM_DISPLAYCHANGE()
然后实现消息处理函数:
void CIGUIDEView::OnDisplayChange(UINT nBitsPerPixel, UINT nWidth, UINT nHeight) { CView::OnDisplayChange(nBitsPerPixel, nWidth, nHeight); // 释放旧资源并重建 SafeRelease(&m_pRenderTarget); CreateRenderTarget(); // 触发重绘 Invalidate(); }
这样可以在系统状态变化时提前处理,避免绘制时才重建的延迟。
总结
核心问题就是Direct2D的硬件加速资源在锁屏后失效,而你的代码没有检测和重建。按照上面的方法处理后,应该就能解决客户区冻结的问题了。
内容的提问来源于stack exchange,提问作者user3045799




