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 <[email protected]>
* 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 <[email protected]>
* 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秒會更新氣泡的內容,氣泡的位置會隨著視窗被拖動而自行 調整, 當視窗處於非啟用狀態時候氣泡自動隱藏。
單滑鼠指向某一個氣泡的時候,該氣泡預設會顯示在視窗最頂端。