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

原生C环境下Windows工具提示通知消息缺失问题求助

原生C工具提示自定义绘制解决方案

接收工具提示WM_NOTIFY通知的关键要求

要让主窗口收到工具提示的NM_CUSTOMDRAW这类通知,得满足这几个核心条件:

  • 工具提示窗口创建:用CreateWindowEx创建时,指定样式为WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,父窗口直接设为主窗口句柄。
  • TOOLINFO配置:
    • hwnd字段必须设为主窗口句柄
    • uFlags要包含TTF_IDISHWND | TTF_SUBCLASS(TTF_SUBCLASS会让工具提示子类化主窗口,确保通知消息能转发到主窗口)
    • 必须调用TTM_ADDTOOL把TOOLINFO注册到工具提示窗口,这一步是消息传递的关键,漏了绝对收不到通知
  • 消息处理:主窗口的WM_NOTIFY回调里,通过NMHDR.hwndFrom匹配工具提示窗口句柄,专门处理NM_CUSTOMDRAW消息。

符合需求的原生C示例代码

以下示例实现:通过WM_MOUSEHOVER激活工具提示,鼠标离开/点击时关闭,支持自定义绘制背景和文本:

#include <windows.h>
#include <commctrl.h>

#define ID_TOOLTIP 1001

HWND g_hTooltip = NULL;
BOOL g_bTipActive = FALSE;

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_CREATE:
            // 创建工具提示窗口
            g_hTooltip = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, 
                WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                hWnd, (HMENU)ID_TOOLTIP, GetModuleHandle(NULL), NULL);
            
            if (g_hTooltip)
            {
                // 配置TOOLINFO
                TOOLINFO ti = {0};
                ti.cbSize = sizeof(TOOLINFO);
                ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
                ti.hwnd = hWnd;
                ti.uId = (UINT_PTR)hWnd;
                ti.lpszText = TEXT("自定义绘制工具提示示例");
                
                // 注册工具到提示窗口
                SendMessage(g_hTooltip, TTM_ADDTOOL, 0, (LPARAM)&ti);
                
                // 设置提示延迟参数
                SendMessage(g_hTooltip, TTM_SETDELAYTIME, TTDT_INITIAL, (LPARAM)MAKELONG(500, 0));
                SendMessage(g_hTooltip, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM)MAKELONG(3000, 0));
            }
            // 初始化鼠标跟踪
            TRACKMOUSEEVENT tme = {sizeof(TRACKMOUSEEVENT), TME_HOVER | TME_LEAVE, hWnd, 0};
            TrackMouseEvent(&tme);
            break;

        case WM_MOUSEHOVER:
            if (!g_bTipActive && g_hTooltip)
            {
                // 设置提示位置为鼠标偏移处
                POINT pt;
                GetCursorPos(&pt);
                SendMessage(g_hTooltip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y + 10));
                
                // 激活跟踪提示
                TOOLINFO ti = {sizeof(TOOLINFO), TTF_IDISHWND, hWnd, (UINT_PTR)hWnd};
                SendMessage(g_hTooltip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
                g_bTipActive = TRUE;
            }
            // 重新注册鼠标离开跟踪
            TRACKMOUSEEVENT tme_leave = {sizeof(TRACKMOUSEEVENT), TME_LEAVE, hWnd, 0};
            TrackMouseEvent(&tme_leave);
            break;

        case WM_MOUSELEAVE:
            if (g_bTipActive && g_hTooltip)
            {
                TOOLINFO ti = {sizeof(TOOLINFO), TTF_IDISHWND, hWnd, (UINT_PTR)hWnd};
                SendMessage(g_hTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti);
                g_bTipActive = FALSE;
            }
            // 重新注册鼠标悬停跟踪
            TRACKMOUSEEVENT tme_hover = {sizeof(TRACKMOUSEEVENT), TME_HOVER | TME_LEAVE, hWnd, 0};
            TrackMouseEvent(&tme_hover);
            break;

        case WM_LBUTTONDOWN:
            // 点击鼠标立即关闭提示
            if (g_bTipActive && g_hTooltip)
            {
                TOOLINFO ti = {sizeof(TOOLINFO), TTF_IDISHWND, hWnd, (UINT_PTR)hWnd};
                SendMessage(g_hTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti);
                g_bTipActive = FALSE;
            }
            break;

        case WM_NOTIFY:
        {
            NMHDR* pNMHDR = (NMHDR*)lParam;
            if (pNMHDR->hwndFrom == g_hTooltip && pNMHDR->code == NM_CUSTOMDRAW)
            {
                NMCUSTOMDRAW* pCD = (NMCUSTOMDRAW*)lParam;
                switch (pCD->dwDrawStage)
                {
                    case CDDS_PREPAINT:
                        // 通知系统需要自定义所有绘制阶段
                        return CDRF_NOTIFYITEMDRAW;
                    
                    case CDDS_ITEMPREPAINT:
                        // 自定义绘制背景
                        RECT rc = pCD->rc;
                        HBRUSH hBrush = CreateSolidBrush(RGB(255, 240, 180));
                        FillRect(pCD->hdc, &rc, hBrush);
                        DeleteObject(hBrush);
                        
                        // 自定义绘制文本
                        SetTextColor(pCD->hdc, RGB(100, 50, 0));
                        SetBkMode(pCD->hdc, TRANSPARENT);
                        DrawText(pCD->hdc, TEXT("自定义工具提示内容"), -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
                        
                        // 通知系统无需默认绘制
                        return CDRF_SKIPDEFAULT;
                }
            }
            break;
        }

        case WM_DESTROY:
            if (g_hTooltip)
            {
                TOOLINFO ti = {sizeof(TOOLINFO), TTF_IDISHWND, hWnd, (UINT_PTR)hWnd};
                SendMessage(g_hTooltip, TTM_DELTOOL, 0, (LPARAM)&ti);
                DestroyWindow(g_hTooltip);
            }
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszClassName = TEXT("TooltipCustomDrawDemo");
    
    RegisterClassEx(&wc);
    
    HWND hWnd = CreateWindowEx(0, TEXT("TooltipCustomDrawDemo"), TEXT("自定义工具提示"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL);
    
    if (!hWnd) return FALSE;
    
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

常见问题排查

  1. 收不到NM_CUSTOMDRAW:检查是否调用了TTM_ADDTOOL,TOOLINFO的hwnd是否为主窗口,uFlags是否包含TTF_SUBCLASS。
  2. 提示透显/绘制异常:自定义绘制时必须先填充背景,设置SetBkMode(TRANSPARENT)后要确保背景已绘制完成,避免和原窗口内容重叠。
  3. 提示隔次显示/超时失效:每次WM_MOUSEHOVER或WM_MOUSELEAVE后要重新注册对应的鼠标跟踪事件,确保状态同步;通过TTM_SETDELAYTIME设置合理的初始化和自动关闭延迟。

内容的提问来源于stack exchange,提问作者Steve Valliere

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 06:44:56