You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:05:35