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的调用也没有问题。观察到自己创建的对话框风格非常复古。

和MSDN上的大相径庭。

后来查阅相关资料。这是由于项目缺少了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。设置每行的宽度,超过此宽度的文本会自动换行。也可以使用\r\n 强制换行。

注意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\nis 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 \r\n 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\nis 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 controlm_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 TOOLINFOm_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 ToolTipsSendMessage(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 tooltipsSetWindowLongPtr(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\nis a very long text string that must be broken into several lines.")
#define MULTILINE_TEXT_TIME TEXT("This\nis 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);elseg_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秒会更新气泡的内容,气泡的位置会随着窗口被拖动而自行 调整, 当窗口处于非激活状态时候气泡自动隐藏。

单鼠标指向某一个气泡的时候,该气泡默认会显示在窗口最顶端。

Windows ToolTips简要介绍相关推荐

  1. linux df命令功能,Linux df命令简要介绍

    日常工作生活中,我们常需要查看系统当前的磁盘空间使用情况.在windows下,只需简单点击我的电脑,就看到带进度条的系统磁盘使用情况,非常直观.那linux命令行下如何实现同样的功能呢?这就是我们今天 ...

  2. python解释器cpython的源码简要介绍

    0. 参考文档 参考文档如下: Your Guide to the CPython Source Code [python]带你入门cpython源代码,让你不再一头雾水!尝试去读读python的实现 ...

  3. 图形化界面设计软件简要介绍

    图形化界面设计软件简要介绍 [摘要]:面向使用者的系统工程设计,其目的是要准确.快速地在设计者和使用者之间传递信息和实现功能,也是优化产品性能来适应大众的操作能力,减轻使用者的认知负担.成功的系统工程 ...

  4. 【SQLite3+Qt开发】SQLite3简要介绍+在Qt5中的使用步骤

    目录 一.SQLite简要介绍 什么是SQLite? 为什么要用SQLite? SQLite下载 二.在Qt5中使用步骤 第一步:导入sqlite3相关文件 第二步:打开qt 修改  .pro文件,添 ...

  5. WAVE音频文件格式及其64位扩展格式的简要介绍

    正文 关于 WAVE 文件格式,网上有不少介绍,但关于WAVE 64位扩展格式的介绍却是几乎没有. 所以本文的目的是简要介绍标准的 WAVE 格式,以及两种主要的扩展格式. 文中所有代码都用C语言来描 ...

  6. 西门子s5 通讯测试软件,几种常用的S5编程软件简要介绍

    主题:几种常用的S5编程软件简要介绍 楼主 2011-05-11 09:06:32 几种常用的S5编程软件说明: Step5 西门子嫡系产品: 肯定100%的满足你对S5全系列的开发.维护要求: DO ...

  7. C语言开发tui,TUI库newt和snack简要介绍

    大多数商业化软件产品一般会通过实现GUI(Graphical User Interface)或者TUI(Text-based User Interface/Textual User Interface ...

  8. HTTP协议简要介绍

    1. 网络基础 TCP/IP 通常使用的网络是在TCP/IP协议簇基础上运作的. HTTP属于它内部的一个子集. TCP/IP分为4个层次, 应用层, 传输层, 网络层, 链路层. (Applicat ...

  9. DNS域名解析简要介绍

    DNS域名解析简要介绍 DNS域名解析简要介绍 首先,什么是DNS: 1,DNS服务器: 部署一个可以被别人访问到的云服务 2,域名解析的过程 DNS域名解析简要介绍 首先,什么是DNS: 1,DNS ...

  10. Hadoop学习笔记一 简要介绍

    Hadoop学习笔记一 简要介绍 这里先大致介绍一下Hadoop.     本文大部分内容都是从官网Hadoop上来的.其中有一篇介绍HDFS的pdf文档,里面对Hadoop介绍的比较全面了.我的这一 ...

最新文章

  1. Android Go初探
  2. 迈入幸福婚姻殿堂的星座女
  3. 计算机控制炉温实验,计算机控制(炉温控制)实验报告.doc
  4. LeetCode: Max Points on a Line
  5. 一文带你了解隐马尔科夫模型
  6. python画锯齿波_用Python控制硬件35-自制二三十元成本的信号测量采集控制系统
  7. Peoplesoft取Translate Value标签值的方法
  8. C++/OpenCV:同颜色检测提取
  9. Flyweight设计模式
  10. 中国土壤全磷含量空间分布数据
  11. Maker工作室_激光雕刻机使用方法
  12. mysql正则防注入_防止sql注入的方法
  13. java等额本息、等额本金计算 记录一下
  14. html布尔属性写法,HTML - 为什么布尔属性没有布尔值?
  15. GPT-3根据输入词执行多句子生成
  16. 火狐浏览器插件开发小试
  17. Review of 2012 Goal for 2013
  18. 生鲜冷链冻品零售企业订货管理系统解决方案
  19. 技美知识学习3700:现代移动端的TBR和TBDR渲染管线
  20. WordPress批量删除文章失效图片_批量删除文章404超链接教程

热门文章

  1. PDF按页拆分为PDF单页或者图片
  2. 2020 11月8周记
  3. mov和mp4格式哪个好_Mac版dvd格式转换器哪个好用?Mac上最好用的dvd格式转换器推荐...
  4. ffmpeg 转换flv压缩大小_ffmpeg 视频压缩 转换
  5. 【转】虚拟机文件格式详解 .VMX .VMSD .VMDK .NVRAM .VMX
  6. 服务器上qq打不开网页,能上qq打不开网页的解决方法
  7. Makefile--如何将当前文件下的所有*.cpp文件与*.c文件全部生成相应的可执行文件
  8. 蓝桥杯 A组 迷宫地图
  9. ecshop 框架 简单分析
  10. ecshop4.0php,ECSHOP安装教程【ECSHOP4.0安装教程】图解ECSHOP4.0安装教程流程和步骤-ECSHOP教程网...