基于Direct2D的定时绘图与逐字符文本绘制技术问询
问题解答
1. Direct2D实现对象延迟绘制(5秒)
你之前用Sleep()或直接在窗口过程里用C++定时器导致程序挂起,核心原因是UI线程不能被阻塞——窗口过程运行在主线程(UI线程)中,Sleep()会卡住消息循环,导致窗口无法响应输入、刷新,表现为程序挂起。
正确的做法是使用Windows系统定时器SetTimer(),步骤如下:
- 在窗口初始化阶段(比如
WM_CREATE消息处理中)调用SetTimer(hwnd, TIMER_ID_DELAY_DRAW, 5000, NULL),设置5秒后触发定时器。 - 在窗口过程中处理
WM_TIMER消息:收到对应ID的定时器消息时,标记一个全局/类成员变量(比如bool shouldDrawDelayedObj = true),再调用InvalidateRect(hwnd, NULL, TRUE)触发窗口重绘。 - 在
WM_PAINT(或Direct2D的绘制逻辑)中检查该标记,若为真则绘制延迟的对象。 - 绘制完成后销毁定时器(
KillTimer(hwnd, TIMER_ID_DELAY_DRAW)),避免重复触发。
示例代码片段:
#define TIMER_ID_DELAY_DRAW 1001 bool g_shouldDrawDelayedObj = false; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CREATE: // 设置5秒后触发的定时器 SetTimer(hwnd, TIMER_ID_DELAY_DRAW, 5000, NULL); break; case WM_TIMER: if(wParam == TIMER_ID_DELAY_DRAW) { g_shouldDrawDelayedObj = true; InvalidateRect(hwnd, NULL, TRUE); KillTimer(hwnd, TIMER_ID_DELAY_DRAW); // 仅触发一次 } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 执行Direct2D基础绘制逻辑(略) if(g_shouldDrawDelayedObj) { // 绘制延迟的目标对象 } EndPaint(hwnd, &ps); break; } // 其他消息处理(略) } return DefWindowProc(hwnd, msg, wParam, lParam); }
2. 逐字符动态绘制文本(DirectWrite/DrawText)
控制台中用WriteConsoleCharacter()加Sleep()能生效,是因为控制台采用阻塞式输出模型,无需维护消息循环;但窗口程序的UI线程必须持续处理消息,否则会失去响应,因此不能直接在主线程中调用Sleep()。
推荐两种实现方式:
方式一:用定时器逐帧更新显示字符数
- 定义全局/类成员变量:
int g_displayCharCount = 0(当前显示的字符数)、const wchar_t* g_fullText = L"目标文本内容"(完整文本)。 - 在
WM_CREATE中设置短间隔定时器(比如150ms,控制字符显示速度)。 - 处理
WM_TIMER:每次触发时g_displayCharCount++,若超过文本长度则销毁定时器,再调用InvalidateRect触发重绘。 - 绘制逻辑中只渲染前
g_displayCharCount个字符:- 用
DrawText()时,截取子字符串传入; - 用DirectWrite时,创建包含前N个字符的
IDWriteTextLayout,再调用DrawTextLayout绘制。
- 用
示例代码片段(DirectWrite版本):
#define TIMER_ID_TYPE_TEXT 1002 int g_displayCharCount = 0; const wchar_t* g_fullText = L"逐字符绘制的示例文本"; IDWriteTextFormat* g_pTextFormat = nullptr; IDWriteFactory* g_pDWriteFactory = nullptr; ID2D1RenderTarget* g_pRenderTarget = nullptr; ID2D1SolidColorBrush* g_pBlackBrush = nullptr; // 初始化Direct2D/DirectWrite资源(略) LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CREATE: // 每150ms显示一个字符 SetTimer(hwnd, TIMER_ID_TYPE_TEXT, 150, NULL); break; case WM_TIMER: if(wParam == TIMER_ID_TYPE_TEXT) { g_displayCharCount++; if(g_displayCharCount > wcslen(g_fullText)) { KillTimer(hwnd, TIMER_ID_TYPE_TEXT); } InvalidateRect(hwnd, NULL, TRUE); } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); if(g_displayCharCount > 0) { // 截取前g_displayCharCount个字符 wchar_t* subText = new wchar_t[g_displayCharCount + 1]; wcsncpy(subText, g_fullText, g_displayCharCount); subText[g_displayCharCount] = L'\0'; IDWriteTextLayout* pTextLayout = nullptr; g_pDWriteFactory->CreateTextLayout( subText, wcslen(subText), g_pTextFormat, 800.0f, 600.0f, &pTextLayout ); // 绘制文本 g_pRenderTarget->DrawTextLayout( D2D1::Point2F(50.0f, 50.0f), pTextLayout, g_pBlackBrush ); SafeRelease(&pTextLayout); delete[] subText; } EndPaint(hwnd, &ps); break; } // 其他消息处理(略) } return DefWindowProc(hwnd, msg, wParam, lParam); }
方式二:异步线程更新(不推荐,需同步)
若想避免定时器,可开启后台线程,每隔一段时间更新显示字符数,再通过PostMessage给主线程发消息触发重绘。但这种方式需要处理线程同步(比如用临界区保护g_displayCharCount),复杂度更高,优先推荐定时器方案。
内容的提问来源于stack exchange,提问作者nore
相关产品推荐
相关产品推荐

