原文转自 https://blog.csdn.net/sesiria/article/details/77450151
Windows 标准控件ToolTips简要介绍
参考文档 MSDN
https://msdn.microsoft.com/en-us/library/ff486072(v=vs.85).aspx
一,什么是ToolTips
ToolTips 就是一个类似于一个悬浮的文本框,在鼠标指针移动上去能显示特定的文本。
各种ToolTips样式。
二,创建ToolTips
HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, hinstMyDll, NULL); SetWindowPos(hwndTip, HWND_TOPMOST,0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
此后ToolTips的窗口函数自动维护Tooltips的尺寸,位置和显示隐藏状态等。 ToolTips的高度基于所设置的字体的高度。
1.激活ToolTips
Tooltips可以处于激活和未激活的状态。激活状态下ToolTips会显示文本。 当ToolTips未激活,其文本讲不被显示。即使鼠标指针放在一个Tools上发送TTM_ACTIVE可以激活和关闭激活一个ToolTips的状态。
2.将ToolTips关联Tools
创建一个TOOLINFO的结构体对象,设置uID为关联工具的ID
设置uFlags为 TTF_IDISHWND
并发送TTM_ADDTOOL消息给ToolTips的句柄。后面的完整的例子。
3.显示文本
默认使用TOOLINFO的 lpszText为显示问题。 可以发送TTM_UPDATETIPTEXT消息来更新显示值。
如果将lpszText 设置为 LPSTR_TEXTCALLBACK ToolTips需要显示文本时候,会Call之前注册的父窗口句柄的窗口函数并发送 TTN_GETDISPINFO通知码,
该消息包含了指向NMTTDISPINFO 结构的指针用于修改相应的文本,以供后续显示使用。
4.消息和通知
windows默认只发送消息给包含鼠标指针的窗口,并不会发送消息给ToolTips。因此需要关联ToolTips和其对应的父窗口活控件ID来控制其显示(恰当的位置和恰当的时间)。
ToolTips会自动处理一下的消息。
1.通过TOOLINFO绑定过的控件或者父窗口的矩形区域。
2.绑定ToolTip的父窗口在同一个线程内。
满足以上两个条件 将TOOLINFO的uFlags设置为 TTF_SUBCLASS 然后发送TTM_ADDTOOL消息给Tooltip的句柄。 但是ToolTips和关联的窗口必须有直接的消息通路。也就是父窗口和子窗口的关系。 如果你关联了别的进程的窗口,还是收不到消息的。可能要使用HOOK。此时你应该发送TTM_RELAYEVENT消息给tooltip 参考Tracking Tooltip
当Tooltip要显示的时候会发送给其拥有者窗口TTN_SHOW通知码。 TTN_POP表明Tooltip即将要隐藏。 通过WM_NOTIFY消息发送。
三,ToolTips应用
1.一个简单的ToolTips的例子
#include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #pragma comment(lib, "comctl32.lib") LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); HINSTANCE g_hInst; HWND hTTWnd; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) { g_hInst = hInstance; INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES }; BOOL ret = InitCommonControlsEx(&cx); return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc); } // Description: // Creates a tooltip for an item in a dialog box. // Parameters: // idTool - identifier of an dialog box item. // nDlg - window handle of the dialog box. // pszText - string to use as the tooltip text. // Returns: // The handle to the tooltip. // HWND CreateToolTip(int toolID, HWND hDlg, PTSTR pszText) { if (!toolID || !hDlg || !pszText) { return FALSE; } // Get the window of the tool. HWND hwndTool = GetDlgItem(hDlg, toolID); // Create the tooltip. g_hInst is the global instance handle. HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg, NULL, g_hInst, NULL); if (!hwndTool || !hwndTip) { return (HWND)NULL; } // Associate the tooltip with the tool. TOOLINFO toolInfo = { 0 }; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = hDlg; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; toolInfo.uId = (UINT_PTR)hwndTool; toolInfo.lpszText = pszText; SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); return hwndTip; } void CreateToolTipForRect(HWND hwndParent) { // Create a tooltip. HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, g_hInst, NULL); SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Set up "tool" information. In this case, the "tool" is the entire parent window. TOOLINFO ti = { 0 }; ti.cbSize = sizeof(TOOLINFO); ti.uFlags = TTF_SUBCLASS; ti.hwnd = hwndParent; ti.hinst = g_hInst; ti.lpszText = TEXT("This is your tooltip string."); GetClientRect(hwndParent, &ti.rect); // Associate the tooltip with the "tool" window. SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { CreateToolTipForRect(hDlg); break; } case WM_CLOSE: EndDialog(hDlg, FALSE); break; } return FALSE; }
这个代码很简单就是在Windows对话框上显示ToolTips。可是编译以后死活不显示,初始化InitCommonControlsEx的调用也没有问题。观察到自己创建的对话框风格非常复古。
后来查阅相关资料。这是由于项目缺少了Manifest定义。在网上找了一个Manifest的定义文件在项目加载此文件就解决了此问题。关于Manifest文件的定义参考此文章
MSDN: Enable Visual Style in your program.
https://msdn.microsoft.com/en-us/library/windows/desktop/bb773175(v=vs.85).aspx#no_extensions
Windows.Manifest定义如下
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity name="Microsoft.Windows.XXXX" processorArchitecture="x86" version="5.1.0.0" type="win32"/> <description>Windows Shell</description> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </assembly>
运行结果如下。
关于这个问题深入研究发现,是调用TOOLINFOW类的时候如果程序加载Common Control 6.0以下的版本,这个结构体的定义的实际size比6.0少4个字节。
而ANSI版本无此问题。如果使用Unicode版本必须加入manifest强制让应用程序加载common Control 6.0才能使用sizeof(TOOLINFOW)的返回值。
否则就要将此值减去4
参考此文章: https://stackoverflow.com/questions/2545682/unicode-tooltips-not-showing-up/15173051
2. 一个指定位置显示Tooltip的例子 同时显示2个Tooltip并且自己定位Toolpis的位置。
#include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #pragma comment(lib, "comctl32.lib") LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); HINSTANCE g_hInst; HWND hTTWnd; HWND g_hwndTrackingTT; HWND g_hwndTrackingTT1; TOOLINFO g_toolItem; BOOL g_TrackingMouse; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) { g_hInst = hInstance; INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES }; BOOL ret = InitCommonControlsEx(&cx); return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc); } // Description: // Creates a tooltip for an item in a dialog box. // Parameters: // idTool - identifier of an dialog box item. // nDlg - window handle of the dialog box. // pszText - string to use as the tooltip text. // Returns: // The handle to the tooltip. // HWND CreateToolTip(int toolID, HWND hDlg, PTSTR pszText) { if (!toolID || !hDlg || !pszText) { return FALSE; } // Get the window of the tool. HWND hwndTool = GetDlgItem(hDlg, toolID); // Create the tooltip. g_hInst is the global instance handle. HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg, NULL, g_hInst, NULL); if (!hwndTool || !hwndTip) { return (HWND)NULL; } // Associate the tooltip with the tool. TOOLINFO toolInfo = { 0 }; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = hDlg; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; toolInfo.uId = (UINT_PTR)hwndTool; toolInfo.lpszText = pszText; SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); return hwndTip; } void CreateToolTipForRect(HWND hwndParent) { // Create a tooltip. HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, g_hInst, NULL); SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Set up "tool" information. In this case, the "tool" is the entire parent window. TOOLINFO ti = { 0 }; ti.cbSize = sizeof(TOOLINFO); ti.uFlags = TTF_SUBCLASS; ti.hwnd = hwndParent; ti.hinst = g_hInst; ti.lpszText = TEXT("This is your tooltip string."); GetClientRect(hwndParent, &ti.rect); // Associate the tooltip with the "tool" window. SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } HWND CreateTrackingToolTip(int toolID, HWND hDlg, WCHAR* pText) { // Create a tooltip. HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg, NULL, g_hInst, NULL); if (!hwndTT) { return NULL; } // Set up the tool information. In this case, the "tool" is the entire parent window. g_toolItem.cbSize = sizeof(TOOLINFO); g_toolItem.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE; g_toolItem.hwnd = hDlg; g_toolItem.hinst = g_hInst; g_toolItem.lpszText = pText; g_toolItem.uId = (UINT_PTR)hDlg; GetClientRect(hDlg, &g_toolItem.rect); // Associate the tooltip with the tool window. SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&g_toolItem); return hwndTT; } LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { g_hwndTrackingTT = CreateTrackingToolTip(0, hDlg, L""); g_hwndTrackingTT1 = CreateTrackingToolTip(0, hDlg, L""); break; } case WM_MOUSELEAVE: SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem); SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem); g_TrackingMouse = FALSE; return FALSE; case WM_MOUSEMOVE: static int oldX, oldY; int newX, newY; if (!g_TrackingMouse) // The mouse has just entered the window. { // Request notification when the mouse leaves. TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; tme.hwndTrack = hDlg; tme.dwFlags = TME_LEAVE; TrackMouseEvent(&tme); // Activate the tooltip. SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem); SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem); g_TrackingMouse = TRUE; } newX = GET_X_LPARAM(lParam); newY = GET_Y_LPARAM(lParam); // Make sure the mouse has actually moved. The presence of the tooltip // causes Windows to send the message continuously. if ((newX != oldX) || (newY != oldY)) { oldX = newX; oldY = newY; // Update the text. WCHAR coords[12]; wsprintf(coords, TEXT("%d, %d"), newX, newY); g_toolItem.lpszText = coords; SendMessage(g_hwndTrackingTT, TTM_SETTOOLINFO, 0, (LPARAM)&g_toolItem); SendMessage(g_hwndTrackingTT1, TTM_SETTOOLINFO, 0, (LPARAM)&g_toolItem); // Position the tooltip. The coordinates are adjusted so that the tooltip does not overlap the mouse pointer. //POINT pt = { newX, newY }; POINT pt = { 50, 50 }; ClientToScreen(hDlg, &pt); SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20)); SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 100, pt.y - 20)); } return FALSE; case WM_CLOSE: EndDialog(hDlg, FALSE); break; } return FALSE; }
运行结果,
可以自行设定ToolTips的位置TTM_TRACKPOSITION,修改ToolTips的显示值TTM_SETTOOLINFO, 控制Tooltips的显示TTM_TRACKACTIVE.
3. 显示多行文本的ToolTips
多行文本ToolTips参考图
使用TTM_SETMAXTIPWIDTH 消息来创建一个多行文本的ToolTips。设置每行的宽度,超过此宽度的文本会自动换行。也可以使用 强制换行。
注意NMTTDISPINFO 的szText成员最多只能存储80个字符。如果要显示长字符串,请用NMTTDISPINFO的lpszText指向一个长文本的字符。
一下例子使用了TTN_GETDISPINFO通知码来修改tooltips的文本。
case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case TTN_GETDISPINFO: LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lParam; SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 150); wcscpy_s(pInfo->szText, ARRAYSIZE(pInfo->szText), L"This is a very long text string " L"that must be broken into several lines."); break; } break; }
测试用例
显示两个固定位置的多行提示框 若窗口处于非激活状态则隐藏。
提示框的位置会随着窗的移动而移动。
#include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <stdio.h> #include "resource.h" #pragma comment(lib, "comctl32.lib") LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); HINSTANCE g_hInst; HWND hTTWnd; HWND g_hwndTrackingTT; HWND g_hwndTrackingTT1; TOOLINFO g_toolItem; BOOL g_TrackingMouse = FALSE; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, int) { g_hInst = hInstance; INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES }; BOOL ret = InitCommonControlsEx(&cx); return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc); } // Description: // Creates a tooltip for an item in a dialog box. // Parameters: // idTool - identifier of an dialog box item. // nDlg - window handle of the dialog box. // pszText - string to use as the tooltip text. // Returns: // The handle to the tooltip. // HWND CreateToolTip(int toolID, HWND hDlg, PTSTR pszText) { if (!toolID || !hDlg || !pszText) { return FALSE; } // Get the window of the tool. HWND hwndTool = GetDlgItem(hDlg, toolID); // Create the tooltip. g_hInst is the global instance handle. HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg, NULL, g_hInst, NULL); if (!hwndTool || !hwndTip) { return (HWND)NULL; } SetWindowPos(hwndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Associate the tooltip with the tool. TOOLINFO toolInfo = { 0 }; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = hDlg; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; toolInfo.uId = (UINT_PTR)hwndTool; toolInfo.lpszText = LPSTR_TEXTCALLBACK; // pszText; SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); return hwndTip; } void CreateToolTipForRect(HWND hwndParent) { // Create a tooltip. HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, g_hInst, NULL); SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Set up "tool" information. In this case, the "tool" is the entire parent window. TOOLINFO ti = { 0 }; ti.cbSize = sizeof(TOOLINFO); ti.uFlags = TTF_SUBCLASS; ti.hwnd = hwndParent; ti.hinst = g_hInst; ti.lpszText = TEXT("This is your tooltip string."); GetClientRect(hwndParent, &ti.rect); // Associate the tooltip with the "tool" window. SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } HWND CreateTrackingToolTip(int toolID, HWND hDlg, WCHAR* pText) { // Create a tooltip. HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg, NULL, g_hInst, NULL); if (!hwndTT) { return NULL; } // Set up the tool information. In this case, the "tool" is the entire parent window. g_toolItem.cbSize = sizeof(TOOLINFO); g_toolItem.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE; g_toolItem.hwnd = hDlg; g_toolItem.hinst = g_hInst; g_toolItem.lpszText = LPSTR_TEXTCALLBACK;//pText; g_toolItem.uId = (UINT_PTR)hDlg; GetClientRect(hDlg, &g_toolItem.rect); // Associate the tooltip with the tool window. SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&g_toolItem); SendMessage(hwndTT, TTM_SETDELAYTIME, (WPARAM)TTDT_AUTOPOP, (LPARAM)MAKELONG(30 * 1000, 0)); return hwndTT; } LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static BOOL bActive = FALSE; switch (uMsg) { case WM_INITDIALOG: { g_hwndTrackingTT = CreateTrackingToolTip(0, hDlg, L""); g_hwndTrackingTT1 = CreateTrackingToolTip(0, hDlg, L""); //hTTWnd = CreateToolTip(IDCANCEL, hDlg, TEXT("IDCANCEL String nextline")); //CreateToolTipForRect(hDlg); break; } case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case TTN_GETDISPINFO: /*TTN_NEEDTEXT:*/ LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lParam; SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 150); wcscpy_s(pInfo->szText, ARRAYSIZE(pInfo->szText), L"This is a very long text string " L"that must be broken into several lines."); break; } break; } case WM_MOVE: { POINT pt = { 50, 50 }; ClientToScreen(hDlg, &pt); SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20)); SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y + 80)); } break; case WM_ACTIVATE: // if the main windows is inactive ,disappear the tooltips. if (LOWORD(wParam) == WA_INACTIVE) { SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem); SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem); bActive = FALSE; } else { SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem); SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem); POINT pt = { 50, 50 }; ClientToScreen(hDlg, &pt); SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20)); SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y + 80)); bActive = TRUE; } break; case WM_MOUSEMOVE: { if (!bActive) break; SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem); SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem); POINT pt = { 50, 50 }; ClientToScreen(hDlg, &pt); SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20)); SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y + 80)); } break; case WM_CLOSE: EndDialog(hDlg, FALSE); break; } return FALSE; }
熟悉了ToolTips的用法可以对其用C++做一个封装方便调用。
自己实现了一个可以自行设定位置的基于Windows ToolTips的类。并且使用了SetWindowsLongPtr自行处理了WM_NOTIFY消息。(主窗口不必关心内部消息处理)
CToolTips.h 头文件
/* CToolTips - CToolTips.h * * Author: Sesiria <stjohnson_free@hotmail.com> * Copyright (c) 2017 Sesiria. * * CToolTips module header file * This module is designed to to encapsulation the behavior of the standard Windows ToolTips control. */ #ifndef _CTOOLTIPS_H_ #define _CTOOLTIPS_H_ #include <windows.h> #include <CommCtrl.h> #include <map> #include <list> #include <string> class CToolTips //Based on the Track style of the ToolTips control. { // member function. public: CToolTips(HWND hParentWnd, HINSTANCE hInstance, bool MultiLine = false); //Normal Style and Multiline ~CToolTips(); void setText(LPCTSTR szText); void setMultiLineText(LPCTSTR szText, const LONG nWidth); void initToolTips(); void setPosition(const POINT& pt); void setVisible(bool bVisible); void setUpdate(); static LRESULT CALLBACK tooltipWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK parentWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); WNDPROC getParentWndProc() { return m_parentWndProc; }; private: // void setActive(bool bActive); void _registerWndProc(); void _unregisterWndProc(); bool _createToolTips(); void _destroyToolTips(); void _addtoInstanceTable(); void _removeFromInstanceTable(); // member variable. private: LPTSTR m_szText; LPTSTR m_multiText; LONG m_nWidth; bool m_bMultiLine; bool m_bActive; bool m_bVisible; POINT m_pos; HWND m_hParent; HWND m_hToolTips; HINSTANCE m_hInst; TOOLINFO m_toolInfo; WNDPROC m_parentWndProc; WNDPROC m_tooltipWndProc; }; #endif // _CTOOLTIPS_H_
CToolTips.cpp 源文件
/* CToolTips - CToolTips.cpp * * Author: Sesiria <stjohnson_free@hotmail.com> * Copyright (c) 2017 Sesiria. * * CToolTips module source file * This module is designed to to encapsulation the behavior of the standard Windows ToolTips control. */ #include "CToolTips.h" #define DELETEP(p) do { if (p) { delete(p); (p)=NULL; } } while (0) #define DELETEPV(pa) do { if (pa) { delete [] (pa); (pa)=NULL; } } while (0) typedef std::list<HWND> ListInstance; // this data struct is used to support different parent dialog bind with the tooltips instance. // the HWND is the parent dialog HWND // ListInstance is a list container to store all the handle of the tooltips relative the same // parent dialog. typedef std::map<HWND, ListInstance*> TableInstance; ///////////////////////////////////////////////////////////// static TableInstance g_tblInstance; bool isInTable(HWND hParent) { TableInstance::iterator iter = g_tblInstance.find(hParent); if (iter == g_tblInstance.end()) return false; return true; } bool isInTable(HWND hParent, HWND hToolTips) { ListInstance * pList = NULL; TableInstance::iterator iter = g_tblInstance.find(hParent); if (iter == g_tblInstance.end()) // the parent window has not been register. { return false; } else // the parent windows has been registered we just get the parent wndproc from the other nodes. { pList = iter->second; HWND hToolTips = *pList->begin(); ListInstance::const_iterator iterList = std::find(pList->begin(), pList->end(), hToolTips); if (iterList == pList->end()) return false; } return true; } HWND getFirstToolTips(HWND hParent) { if (!isInTable(hParent)) return NULL; ListInstance * pList = NULL; TableInstance::iterator iter = g_tblInstance.find(hParent); return *iter->second->begin(); } CToolTips::CToolTips(HWND hParentWnd, HINSTANCE hInstance, bool MultiLine /*= false*/) :m_szText(NULL), m_multiText(NULL), m_nWidth(0), m_bMultiLine(MultiLine), m_bActive(false), m_bVisible(false), m_hParent(hParentWnd), m_hToolTips(NULL), m_parentWndProc(NULL), m_hInst(hInstance) { m_pos.x = 0; m_pos.y = 0; memset(&m_toolInfo, 0, sizeof(TOOLINFO)); if (_createToolTips()) { _registerWndProc(); _addtoInstanceTable(); } } CToolTips::~CToolTips() { _removeFromInstanceTable(); _unregisterWndProc(); if (m_hToolTips) { DestroyWindow(m_hToolTips); m_hToolTips = NULL; } DELETEPV(m_szText); DELETEPV(m_multiText); } bool CToolTips::_createToolTips() { if (!m_hParent || !m_hInst) return false; // Create the Handle for the ToolTips control m_hToolTips = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hParent, NULL, m_hInst, NULL); return (m_hToolTips != NULL); } void CToolTips::initToolTips() { if (!m_hToolTips || !m_hInst) return; SetWindowPos(m_hToolTips, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); //Init the TOOLINFO m_toolInfo.cbSize = sizeof(TOOLINFO); m_toolInfo.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE; m_toolInfo.hwnd = m_hParent; m_toolInfo.hinst = m_hInst; m_toolInfo.lpszText = LPSTR_TEXTCALLBACK; m_toolInfo.uId = (UINT_PTR)m_hParent; // Associate the tooltip with the tool window. SendMessage(m_hToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&m_toolInfo); // Set the DelayTime of the ToolTips SendMessage(m_hToolTips, TTM_SETDELAYTIME, (WPARAM)TTDT_AUTOPOP, (LPARAM)MAKELONG(30 * 1000, 0)); // By default, we just set the tooltips to inactive. SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&m_toolInfo); m_bActive = true; } void CToolTips::_addtoInstanceTable() { ListInstance * pList = NULL; TableInstance::iterator iter = g_tblInstance.find(m_hParent); if (iter == g_tblInstance.end()) // the parent window has not been register. { pList = new ListInstance; pList->push_back(m_hToolTips); g_tblInstance.insert(std::make_pair(m_hParent, pList)); } else { pList = iter->second; ListInstance::const_iterator iterSet = std::find(pList->begin(), pList->end(), m_hToolTips); if (iterSet == pList->end()) pList->push_back(m_hToolTips); } } void CToolTips::_removeFromInstanceTable() { TableInstance::iterator iter = g_tblInstance.find(m_hParent); if (iter == g_tblInstance.end()) return; ListInstance * pSet = iter->second; pSet->remove(m_hToolTips); } void CToolTips::_registerWndProc() { // bind the this pointer to the handle of the tooltips SetWindowLongPtr(m_hToolTips, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)); // Register the windows proc for the tooltip dialog. m_tooltipWndProc = (WNDPROC)SetWindowLongPtr(m_hToolTips, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&CToolTips::tooltipWndProc)); // Register the windows proc for the parents dialog. if (!isInTable(m_hParent) && !m_parentWndProc) { m_parentWndProc = (WNDPROC)SetWindowLongPtr(m_hParent, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&CToolTips::parentWndProc)); } else { HWND hToolTips; if (!(hToolTips = getFirstToolTips(m_hParent))) return; LONG_PTR user_data = GetWindowLongPtr(hToolTips, GWLP_USERDATA); CToolTips *pToolTips = reinterpret_cast<CToolTips*>(user_data); m_parentWndProc = pToolTips->getParentWndProc(); } } void CToolTips::_unregisterWndProc() { // if it is the last element relative to the parent dialog just unregister the wndproc. TableInstance::iterator iter = g_tblInstance.find(m_hParent); if (iter != g_tblInstance.end() && m_parentWndProc != NULL) { ListInstance *pSet = iter->second; if (pSet->size() == 0)// it is the empty set. { (WNDPROC)SetWindowLongPtr(m_hParent, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_parentWndProc)); g_tblInstance.erase(iter); DELETEP(pSet); } m_parentWndProc = NULL; } // unregister the window procedure and restore to the default procedure. if (m_tooltipWndProc) { SetWindowLongPtr(m_hToolTips, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_tooltipWndProc)); m_tooltipWndProc = NULL; } // unregister the this pointer to the hwnd GWL_USERDATA SetWindowLongPtr(m_hToolTips, GWLP_USERDATA, NULL); } LRESULT CALLBACK CToolTips::tooltipWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { LONG_PTR user_data = GetWindowLongPtr(hWnd, GWLP_USERDATA); CToolTips *this_window = reinterpret_cast<CToolTips*>(user_data); if (!this_window || !this_window->m_tooltipWndProc) return DefWindowProc(hWnd, Msg, wParam, lParam); static bool g_TrackingMouse = false; switch (Msg) { case WM_MOUSELEAVE: g_TrackingMouse = false; return DefWindowProc(hWnd, Msg, wParam, lParam); break; case WM_MOUSEMOVE: if (!g_TrackingMouse) { TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; tme.hwndTrack = hWnd; tme.dwFlags = TME_LEAVE; TrackMouseEvent(&tme); this_window->setUpdate(); g_TrackingMouse = true; } break; } return CallWindowProc(this_window->m_tooltipWndProc, hWnd, Msg, wParam, lParam); } // hook for the parent window procedure LRESULT CALLBACK CToolTips::parentWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { LONG_PTR user_data = GetWindowLongPtr(getFirstToolTips(hWnd), GWLP_USERDATA); CToolTips *this_window = reinterpret_cast<CToolTips*>(user_data); if (!this_window || !this_window->getParentWndProc()) return DefWindowProcW(hWnd, Msg, wParam, lParam); switch (Msg) { case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case TTN_GETDISPINFO: /*TTN_NEEDTEXT:*/ LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lParam; SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, this_window->m_nWidth); lstrcpyn(pInfo->szText, this_window->m_multiText, ARRAYSIZE(pInfo->szText)); break; } } break; case WM_MOVE: { TableInstance::iterator iter = g_tblInstance.find(hWnd); if (iter == g_tblInstance.end()) break; ListInstance *pList = iter->second; ListInstance::iterator iterlist; for (iterlist = pList->begin(); iterlist != pList->end(); ++iterlist) { LONG_PTR user_data = GetWindowLongPtr((HWND)(*iterlist), GWLP_USERDATA); CToolTips *tooltips_window = reinterpret_cast<CToolTips*>(user_data); if (!tooltips_window) continue; tooltips_window->setPosition(tooltips_window->m_pos); } } break; case WM_ACTIVATE: { TableInstance::iterator iter = g_tblInstance.find(hWnd); if (iter == g_tblInstance.end()) break; ListInstance *pList = iter->second; ListInstance::iterator iterlist; for (iterlist = pList->begin(); iterlist != pList->end(); ++iterlist) { LONG_PTR user_data = GetWindowLongPtr((HWND)(*iterlist), GWLP_USERDATA); CToolTips *tooltips_window = reinterpret_cast<CToolTips*>(user_data); if (!tooltips_window) continue; if (LOWORD(wParam) == WA_INACTIVE) { tooltips_window->setActive(false); } else { tooltips_window->setActive(true); } } } } return CallWindowProc(this_window->m_parentWndProc, hWnd, Msg, wParam, lParam); } void CToolTips::setText(LPCTSTR szText) { DELETEPV(m_szText); m_szText = new TCHAR[lstrlen(szText) + 1]; lstrcpy(m_szText, szText); } void CToolTips::setMultiLineText(LPCTSTR szText, const LONG nWidth) { DELETEPV(m_multiText); m_multiText = new TCHAR[lstrlen(szText) + 1]; lstrcpy(m_multiText, szText); m_nWidth = nWidth; if (m_bActive) { setUpdate(); } } void CToolTips::setPosition(const POINT& pt) { m_pos.x = pt.x; m_pos.y = pt.y; POINT newPt = { m_pos.x, m_pos.y}; ClientToScreen(m_hParent, &newPt); SendMessage(m_hToolTips, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(newPt.x, newPt.y)); } void CToolTips::setActive(bool bActive) { m_bActive = bActive; if (m_bActive && m_bVisible) { SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&m_toolInfo); setPosition(m_pos); } else { SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&m_toolInfo); } } void CToolTips::setVisible(bool bVisible) { m_bVisible = bVisible; if (m_bVisible && m_bActive) { SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&m_toolInfo); setPosition(m_pos); } else { SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&m_toolInfo); } } void CToolTips::setUpdate() { SendMessage(m_hToolTips, TTM_UPDATE, 0, 0); }
main.cpp 测试代码
#include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <stdio.h> #include <assert.h> #include "resource.h" #include "CToolTips.h" #pragma comment(lib, "comctl32.lib") #define MULTILINE_TEXT TEXT("This is a very long text string that must be broken into several lines.") #define MULTILINE_TEXT_TIME TEXT("This is a very long text string that must be broken into several lines. %d") LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc1(HWND, UINT, WPARAM, LPARAM); HINSTANCE g_hInst; CToolTips *g_pToolTips = NULL; CToolTips *g_pToolTips1 = NULL; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, int) { g_hInst = hInstance; INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES }; BOOL ret = InitCommonControlsEx(&cx); return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc1); } void TEST_Constructor(HWND hDlg) { g_pToolTips = new CToolTips(hDlg, g_hInst, true); assert(g_pToolTips != NULL); g_pToolTips1 = new CToolTips(hDlg, g_hInst, true); } void TEST_Destructor() { if (g_pToolTips) { delete g_pToolTips; g_pToolTips = NULL; } if (g_pToolTips1) { delete g_pToolTips1; g_pToolTips1 = NULL; } } void TEST_MultilineToolTips() { if (!g_pToolTips) return; g_pToolTips->setMultiLineText(MULTILINE_TEXT, 150); g_pToolTips->initToolTips(); POINT pt = { 50, 50 }; g_pToolTips->setPosition(pt); g_pToolTips->setVisible(true); if (!g_pToolTips1) return; g_pToolTips1->setMultiLineText(MULTILINE_TEXT, 150); g_pToolTips1->initToolTips(); POINT pt1 = { 100, 50 }; g_pToolTips1->setPosition(pt1); g_pToolTips1->setVisible(true); } void TEST_StartDynamicUpdate(HWND HDlg) { SetTimer(HDlg, 1, 5000, NULL); } void TEST_DynamicUpdateToolTips(HWND hDlg) { TCHAR buf[255] = { 0 }; wsprintf(buf, MULTILINE_TEXT_TIME, GetCurrentTime()); // // static int i = 0; if (i % 2 == 0) g_pToolTips1->setMultiLineText(buf, 150); else g_pToolTips->setMultiLineText(buf, 150); i++; } void TEST_KillDynamicUpdate(HWND hDlg) { KillTimer(hDlg, 1); } LRESULT CALLBACK MyDlgProc1(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static CToolTips * pToolTips; switch (uMsg) { case WM_INITDIALOG: TEST_Constructor(hDlg); TEST_MultilineToolTips(); TEST_StartDynamicUpdate(hDlg); break; case WM_TIMER: TEST_DynamicUpdateToolTips(hDlg); break; case WM_CLOSE: TEST_KillDynamicUpdate(hDlg); TEST_Destructor(); EndDialog(hDlg, FALSE); break; } return FALSE; }
运行结果如下
创建了两个多行气泡的ToolTips, 定时器每间隔5秒会更新气泡的内容,气泡的位置会随着窗口被拖动而自行 调整, 当窗口处于非激活状态时候气泡自动隐藏。
单鼠标指向某一个气泡的时候,该气泡默认会显示在窗口最顶端。