原文转自 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,, , , ,
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 = { };
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, , (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, , , , ,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Set up "tool" information. In this case, the "tool" is the entire parent window. TOOLINFO ti = { };
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, , (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 = { };
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, , (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, , , , ,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Set up "tool" information. In this case, the "tool" is the entire parent window. TOOLINFO ti = { };
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, , (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, , (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(, hDlg, L"");
g_hwndTrackingTT1 = CreateTrackingToolTip(, 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[];
wsprintf(coords, TEXT("%d, %d"), newX, newY); g_toolItem.lpszText = coords;
SendMessage(g_hwndTrackingTT, TTM_SETTOOLINFO, , (LPARAM)&g_toolItem);
SendMessage(g_hwndTrackingTT1, TTM_SETTOOLINFO, , (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 = { , };
ClientToScreen(hDlg, &pt);
SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y - ));
SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y - ));
}
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, , );
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, , , , ,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
// Associate the tooltip with the tool.
TOOLINFO toolInfo = { };
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, , (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, , , , ,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Set up "tool" information. In this case, the "tool" is the entire parent window. TOOLINFO ti = { };
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, , (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, , (LPARAM)(LPTOOLINFO)&g_toolItem);
SendMessage(hwndTT, TTM_SETDELAYTIME, (WPARAM)TTDT_AUTOPOP, (LPARAM)MAKELONG( * , )); 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(, hDlg, L"");
g_hwndTrackingTT1 = CreateTrackingToolTip(, 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, , );
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 = { , };
ClientToScreen(hDlg, &pt);
SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y - ));
SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y + ));
} 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 = { , };
ClientToScreen(hDlg, &pt);
SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y - ));
SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y + ));
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 = { , };
ClientToScreen(hDlg, &pt);
SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y - ));
SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, , (LPARAM)MAKELONG(pt.x + , pt.y + )); }
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(),
m_bMultiLine(MultiLine),
m_bActive(false),
m_bVisible(false),
m_hParent(hParentWnd),
m_hToolTips(NULL),
m_parentWndProc(NULL),
m_hInst(hInstance)
{
m_pos.x = ;
m_pos.y = ;
memset(&m_toolInfo, , 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, , , , ,
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, , (LPARAM)(LPTOOLINFO)&m_toolInfo); // Set the DelayTime of the ToolTips
SendMessage(m_hToolTips, TTM_SETDELAYTIME, (WPARAM)TTDT_AUTOPOP, (LPARAM)MAKELONG( * , )); // 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() == )// 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, , 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) + ];
lstrcpy(m_szText, szText);
} void CToolTips::setMultiLineText(LPCTSTR szText, const LONG nWidth)
{
DELETEPV(m_multiText);
m_multiText = new TCHAR[lstrlen(szText) + ];
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, , (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, , );
}

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, );
g_pToolTips->initToolTips();
POINT pt = { , };
g_pToolTips->setPosition(pt);
g_pToolTips->setVisible(true); if (!g_pToolTips1)
return;
g_pToolTips1->setMultiLineText(MULTILINE_TEXT, );
g_pToolTips1->initToolTips();
POINT pt1 = { , };
g_pToolTips1->setPosition(pt1);
g_pToolTips1->setVisible(true);
} void TEST_StartDynamicUpdate(HWND HDlg)
{
SetTimer(HDlg, , , NULL);
}
void TEST_DynamicUpdateToolTips(HWND hDlg)
{
TCHAR buf[] = { };
wsprintf(buf, MULTILINE_TEXT_TIME, GetCurrentTime());
//
//
static int i = ;
if (i % == )
g_pToolTips1->setMultiLineText(buf, );
else
g_pToolTips->setMultiLineText(buf, );
i++;
} void TEST_KillDynamicUpdate(HWND hDlg)
{
KillTimer(hDlg, );
} 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. Windows的SEH机理简要介绍

    1.异常分类 一般来说,我们把Exception分为2类,一类是CPU产生的异常,我们称之为CPU异常(或者硬件异常).另一类为是通过调用RaiseException API产生的软件异常,我们称之为 ...

  2. HTTP协议简要介绍

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

  3. Windos下的6种IO模型简要介绍

    windows进行数据的收发有6种IO模型.分别是阻塞(blocking)模型,选择(select)模型,异步选择(WSAAsyncSelect)模型,事件选择(WSAEventSelect )模型, ...

  4. WCF简要介绍

    什么是WCF WCF的全称是:Windows Communication Foundation.从本质上来说,它是一套软件开发包,是微软公司推出的符合SOA思想的技术框架.WCF为程序员提供了丰富的功 ...

  5. 简要介绍BASE64、MD5、SHA、HMAC几种方法。

    加密解密,曾经是我一个毕业设计的重要组件.在工作了多年以后回想当时那个加密.解密算法,实在是太单纯了.     言归正传,这里我们主要描述Java已经实现的一些加密解密算法,最后介绍数字证书.     ...

  6. [转]Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

    转自:Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划 前面我们从Android应用程序与SurfaceFlinger服务的关系出发,从侧面简单学习了Surfa ...

  7. [转] Android资源管理框架(Asset Manager)简要介绍和学习计划

    转自:http://blog.csdn.net/luoshengyang/article/details/8738877 Android应用程序主要由两部分内容组成:代码和资源.资源主要就是指那些与U ...

  8. Activity启动过程简要介绍

    无论是通过点击应用程序图标来启动Activity,还是通过Activity内部调用startActivity接口来启动新的Activity,都要借助于应用程序框架层的ActivityManagerSe ...

  9. Android应用程序的Activity启动过程简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6685853 在Android系统中,Activ ...

随机推荐

  1. PHP.TP框架下商品项目的优化3-php封装下拉框函数

    php封装下拉框函数 因为在项目中会经常使用到下拉框,所以根据一个表中的数据制作下拉框函数,以便调用 //使用一个表的数据做下拉框函数 function buildSelect($tableName, ...

  2. SpringMVC---web.xml配置详解

    web.xml中需要配置的内容 1.配置监听器<listener> 它有两个监听器: 1). <!--配置文件加载监听器--> <listener> <lis ...

  3. P1021 邮票面值设计

    P1021 邮票面值设计 题目描述 给定一个信封,最多只允许粘贴N张邮票,计算在给定K(N+K≤15)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值MAX,使在1-MAX ...

  4. Eclipse 创建 Java 类---Eclipse教程第10课

    打开新建 Java 类向导 你可以使用新建 Java 类向导来创建 Java 类,可以通过以下途径打开 Java 类向导: 点击 "File" 菜单并选择 New > Cla ...

  5. 剑指Offer - 九度1350 - 二叉树的深度

    剑指Offer - 九度1350 - 二叉树的深度2013-11-23 00:54 题目描述: 输入一棵二叉树,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的 ...

  6. win 7 查看端口被占用

    开始---->运行---->cmd,或者是window+R组合键,调出命令窗口     输入命令:netstat -ano,列出所有端口的情况.在列表中我们观察被占用的端口,比如是4915 ...

  7. Python学习4,字符串

    字符串这个东西主要靠记,多打打就好了. _author_ = "Happyboy" name = "my \tname is happyboy and i am 66 y ...

  8. day01--python基础1

    # 01讲   - Windows下执行程序,必须加 PYTHON.在LINUX下,可以不指明是PYTHON.但是,执行钱许给予hello.py执行权限. - 其次,只要变成可执行程序,必须第一行事前 ...

  9. RPG游戏黑暗之光

    1.设置默认鼠标光标 PlayerSettings → Default Cursor 下设置 2.为人物创建单一类 为人物创建了PlayerAnimation.cs.PlayerDir.cs.Play ...

  10. ftrace 简介

    ftrace 简介 ftrace 的作用是帮助开发人员了解 Linux 内核的运行时行为,以便进行故障调试或性能分析. 最早 ftrace 是一个 function tracer,仅能够记录内核的函数 ...