原生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; }
常见问题排查
- 收不到NM_CUSTOMDRAW:检查是否调用了
TTM_ADDTOOL,TOOLINFO的hwnd是否为主窗口,uFlags是否包含TTF_SUBCLASS。 - 提示透显/绘制异常:自定义绘制时必须先填充背景,设置
SetBkMode(TRANSPARENT)后要确保背景已绘制完成,避免和原窗口内容重叠。 - 提示隔次显示/超时失效:每次
WM_MOUSEHOVER或WM_MOUSELEAVE后要重新注册对应的鼠标跟踪事件,确保状态同步;通过TTM_SETDELAYTIME设置合理的初始化和自动关闭延迟。
内容的提问来源于stack exchange,提问作者Steve Valliere
相关产品推荐
相关产品推荐

