【UX专题】说明气泡Tooltips

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了【UX专题】说明气泡Tooltips相关的知识,希望对你有一定的参考价值。

参考技术A Tooltips : Element翻译做“文字提醒”。轻度交互,hover激活

popup tips :可翻译做 “弹窗提醒”。轻度交互,点击激活

popover : Element翻译做“弹出框”,是基于VUE开发的,与Tooltips类似。可嵌套多种类型信息、操作,可hover激活、click激活。

Popconfirm : Element翻译做“气泡确认框”。点击弹出气泡确认框

ToolTips 是一个类似于一个 悬浮的文本框 ,在鼠标指针移动上去能显示特定的文本

Tooltips可以处于激活和未激活的状态。如果它们在这些状态之间转换,应该会很快发生(150毫秒或更少)。

Tooltips可以依附于页面中任何激活的元素(图标、文字链接、按钮,等等)。它们为配对的元素提供描述或解释。因此,tooltips与界面中的元素相关联并具有特定性

并不会用它来解释大图或整个的任务流。

在页面中主动弹出来告知用户新的功能或如何使用一个具体的功能的提示不是tooltips。

由于tooltips是由悬停手势触发出来的,他们只能在设备上通过鼠标或键盘触发。在触摸屏上通常不可用。(将来,tooltips可以在眼控设备上触发,当用户将视线聚焦在界面某个特定元素 一段时间 便可触发)

没有tooltips的帮助用户也应可以完成任务。tooltips用来解释一些不常见的表单字段,但是字段要求不应该放在tooltips中。如图,不应将密码格式要求放在tooltips中,但是可以在tooltips照片那个说明这个字段的作用。

带文字的图标这类很明显的信息就不要再用tooltips说明了,显得冗余

维基百科中的tooltips,鼠标和键盘都可以触发

4、当页面上相邻的多个元素都有hover时,使用带箭头的tooltips样式,避免混淆

5、界面中的tooltips要有一致性。tooltips需要用户主动hover才能触发,所以要有一致的规律才便于用户发现它们。

1、为没有文字的图片提供tooltips

2、tooltips的颜色要与背景有对比性

3、不要让tooltips遮挡了界面中原本的内容,否则用户需要吧鼠标移开再移回来

越是追求极简,tooltips就越多,用户学习成本越高。所以使用tooltips时想一下:用户在完成任务时一定需要这个信息吗?如果不需要你就可以使用tooltips,否则请直接放到页面中。

参考资料:NNG 《 Tooltip Guidelines 》

Windows ToolTips简要介绍

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 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\\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);
	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秒会更新气泡的内容,气泡的位置会随着窗口被拖动而自行 调整, 当窗口处于非激活状态时候气泡自动隐藏。

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




以上是关于【UX专题】说明气泡Tooltips的主要内容,如果未能解决你的问题,请参考以下文章

排序专题之交换排序

标记点下方添加气泡

如何根据交叉点的大小在 Plotly 中构建具有气泡大小的气泡图?

arcgis api for flex之专题图制作(饼状图,柱状图等)

源码案例

高并发编程专题说明