zoukankan      html  css  js  c++  java
  • Windows ToolTips简要介绍(转)

    原文转自 https://blog.csdn.net/sesiria/article/details/77450151

    Windows 标准控件ToolTips简要介绍

    参考文档 MSDN

    https://msdn.microsoft.com/en-us/library/ff486072(v=vs.85).aspx

     

    一,什么是ToolTips

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

    各种ToolTips样式。

    二,创建ToolTips

    HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
                                WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                hwndParent, NULL, hinstMyDll,
                                NULL);
    
    SetWindowPos(hwndTip, HWND_TOPMOST,0, 0, 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

    此后ToolTips的窗口函数自动维护Tooltips的尺寸,位置和显示隐藏状态等。 ToolTips的高度基于所设置的字体的高度。

     

    1.激活ToolTips

    Tooltips可以处于激活和未激活的状态。激活状态下ToolTips会显示文本。 当ToolTips未激活,其文本讲不被显示。即使鼠标指针放在一个Tools上发送TTM_ACTIVE可以激活和关闭激活一个ToolTips的状态。

     

    2.将ToolTips关联Tools

    创建一个TOOLINFO的结构体对象,设置uID为关联工具的ID

    设置uFlags为  TTF_IDISHWND

    并发送TTM_ADDTOOL消息给ToolTips的句柄。后面的完整的例子。

     

    3.显示文本

    默认使用TOOLINFO的  lpszText为显示问题。 可以发送TTM_UPDATETIPTEXT消息来更新显示值。

    如果将lpszText 设置为 LPSTR_TEXTCALLBACK ToolTips需要显示文本时候,会Call之前注册的父窗口句柄的窗口函数并发送 TTN_GETDISPINFO通知码,

    该消息包含了指向NMTTDISPINFO 结构的指针用于修改相应的文本,以供后续显示使用。

     

    4.消息和通知

    windows默认只发送消息给包含鼠标指针的窗口,并不会发送消息给ToolTips。因此需要关联ToolTips和其对应的父窗口活控件ID来控制其显示(恰当的位置和恰当的时间)。

    ToolTips会自动处理一下的消息。

    1.通过TOOLINFO绑定过的控件或者父窗口的矩形区域。

    2.绑定ToolTip的父窗口在同一个线程内。

    满足以上两个条件 将TOOLINFO的uFlags设置为 TTF_SUBCLASS  然后发送TTM_ADDTOOL消息给Tooltip的句柄。  但是ToolTips和关联的窗口必须有直接的消息通路。也就是父窗口和子窗口的关系。 如果你关联了别的进程的窗口,还是收不到消息的。可能要使用HOOK。此时你应该发送TTM_RELAYEVENT消息给tooltip 参考Tracking Tooltip

     

    当Tooltip要显示的时候会发送给其拥有者窗口TTN_SHOW通知码。 TTN_POP表明Tooltip即将要隐藏。  通过WM_NOTIFY消息发送。

     

    三,ToolTips应用

     

    1.一个简单的ToolTips的例子

    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include "resource.h"
    #pragma comment(lib, "comctl32.lib")
    
    LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);
    
    HINSTANCE g_hInst;
    HWND hTTWnd;
    
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
    {
        g_hInst = hInstance;
        INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES };
        BOOL ret = InitCommonControlsEx(&cx);
        return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc);
    }
    
    
    // Description:
    //   Creates a tooltip for an item in a dialog box. 
    // Parameters:
    //   idTool - identifier of an dialog box item.
    //   nDlg - window handle of the dialog box.
    //   pszText - string to use as the tooltip text.
    // Returns:
    //   The handle to the tooltip.
    //
    HWND CreateToolTip(int toolID, HWND hDlg, PTSTR pszText)
    {
        if (!toolID || !hDlg || !pszText)
        {
            return FALSE;
        }
        // Get the window of the tool.
        HWND hwndTool = GetDlgItem(hDlg, toolID);
    
        // Create the tooltip. g_hInst is the global instance handle.
        HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
            CW_USEDEFAULT, CW_USEDEFAULT,
            CW_USEDEFAULT, CW_USEDEFAULT,
            hDlg, NULL,
            g_hInst, NULL);
    
        if (!hwndTool || !hwndTip)
        {
            return (HWND)NULL;
        }
    
        // Associate the tooltip with the tool.
        TOOLINFO toolInfo = { 0 };
        toolInfo.cbSize = sizeof(toolInfo);
        toolInfo.hwnd = hDlg;
        toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
        toolInfo.uId = (UINT_PTR)hwndTool;
        toolInfo.lpszText = pszText;
        SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
    
        return hwndTip;
    }
    
    void CreateToolTipForRect(HWND hwndParent)
    {
        // Create a tooltip.
        HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            hwndParent, NULL, g_hInst, NULL);
    
        SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0,
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
    
        // Set up "tool" information. In this case, the "tool" is the entire parent window.
    
        TOOLINFO ti = { 0 };
        ti.cbSize = sizeof(TOOLINFO);
        ti.uFlags = TTF_SUBCLASS;
        ti.hwnd = hwndParent;
        ti.hinst = g_hInst;
        ti.lpszText = TEXT("This is your tooltip string.");
    
        GetClientRect(hwndParent, &ti.rect);
    
        // Associate the tooltip with the "tool" window.
        SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
    }
    
    LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_INITDIALOG:
        {
            CreateToolTipForRect(hDlg);
    
            break;
        }
        case WM_CLOSE:
            EndDialog(hDlg, FALSE);
            break;
        }
        return FALSE;
    }

    这个代码很简单就是在Windows对话框上显示ToolTips。可是编译以后死活不显示,初始化InitCommonControlsEx的调用也没有问题。观察到自己创建的对话框风格非常复古。

    后来查阅相关资料。这是由于项目缺少了Manifest定义。在网上找了一个Manifest的定义文件在项目加载此文件就解决了此问题。关于Manifest文件的定义参考此文章

    MSDN: Enable Visual Style in your program.

    https://msdn.microsoft.com/en-us/library/windows/desktop/bb773175(v=vs.85).aspx#no_extensions

    Windows.Manifest定义如下

      <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
      <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
      <assemblyIdentity 
      name="Microsoft.Windows.XXXX" 
      processorArchitecture="x86" 
      version="5.1.0.0" 
      type="win32"/> 
      <description>Windows Shell</description> 
      <dependency> 
      <dependentAssembly> 
      <assemblyIdentity 
      type="win32" 
      name="Microsoft.Windows.Common-Controls" 
      version="6.0.0.0" 
      processorArchitecture="x86" 
      publicKeyToken="6595b64144ccf1df" 
      language="*" 
       /> 
      </dependentAssembly> 
      </dependency> 
      </assembly>

    运行结果如下。

    关于这个问题深入研究发现,是调用TOOLINFOW类的时候如果程序加载Common Control 6.0以下的版本,这个结构体的定义的实际size比6.0少4个字节。

    而ANSI版本无此问题。如果使用Unicode版本必须加入manifest强制让应用程序加载common Control 6.0才能使用sizeof(TOOLINFOW)的返回值。

    否则就要将此值减去4

    参考此文章:  https://stackoverflow.com/questions/2545682/unicode-tooltips-not-showing-up/15173051

     

    2. 一个指定位置显示Tooltip的例子 同时显示2个Tooltip并且自己定位Toolpis的位置。

    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include "resource.h"
    #pragma comment(lib, "comctl32.lib")
    
    LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);
    
    HINSTANCE g_hInst;
    HWND hTTWnd;
    HWND g_hwndTrackingTT;
    HWND g_hwndTrackingTT1;
    TOOLINFO g_toolItem;
    BOOL g_TrackingMouse;
    
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
    {
        g_hInst = hInstance;
        INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES };
        BOOL ret = InitCommonControlsEx(&cx);
        return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc);
    }
    
    
    // Description:
    //   Creates a tooltip for an item in a dialog box. 
    // Parameters:
    //   idTool - identifier of an dialog box item.
    //   nDlg - window handle of the dialog box.
    //   pszText - string to use as the tooltip text.
    // Returns:
    //   The handle to the tooltip.
    //
    HWND CreateToolTip(int toolID, HWND hDlg, PTSTR pszText)
    {
        if (!toolID || !hDlg || !pszText)
        {
            return FALSE;
        }
        // Get the window of the tool.
        HWND hwndTool = GetDlgItem(hDlg, toolID);
    
        // Create the tooltip. g_hInst is the global instance handle.
        HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
            CW_USEDEFAULT, CW_USEDEFAULT,
            CW_USEDEFAULT, CW_USEDEFAULT,
            hDlg, NULL,
            g_hInst, NULL);
    
        if (!hwndTool || !hwndTip)
        {
            return (HWND)NULL;
        }
    
        // Associate the tooltip with the tool.
        TOOLINFO toolInfo = { 0 };
        toolInfo.cbSize = sizeof(toolInfo);
        toolInfo.hwnd = hDlg;
        toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
        toolInfo.uId = (UINT_PTR)hwndTool;
        toolInfo.lpszText = pszText;
        SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
    
        return hwndTip;
    }
    
    void CreateToolTipForRect(HWND hwndParent)
    {
        // Create a tooltip.
        HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            hwndParent, NULL, g_hInst, NULL);
    
        SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0,
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
    
        // Set up "tool" information. In this case, the "tool" is the entire parent window.
    
        TOOLINFO ti = { 0 };
        ti.cbSize = sizeof(TOOLINFO);
        ti.uFlags = TTF_SUBCLASS;
        ti.hwnd = hwndParent;
        ti.hinst = g_hInst;
        ti.lpszText = TEXT("This is your tooltip string.");
    
        GetClientRect(hwndParent, &ti.rect);
    
        // Associate the tooltip with the "tool" window.
        SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
    }
    
    HWND CreateTrackingToolTip(int toolID, HWND hDlg, WCHAR* pText)
    {
        // Create a tooltip.
        HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            hDlg, NULL, g_hInst, NULL);
    
        if (!hwndTT)
        {
            return NULL;
        }
    
        // Set up the tool information. In this case, the "tool" is the entire parent window.
    
        g_toolItem.cbSize = sizeof(TOOLINFO);
        g_toolItem.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE;
        g_toolItem.hwnd = hDlg;
        g_toolItem.hinst = g_hInst;
        g_toolItem.lpszText = pText;
        g_toolItem.uId = (UINT_PTR)hDlg;
    
        GetClientRect(hDlg, &g_toolItem.rect);
    
        // Associate the tooltip with the tool window.
    
        SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&g_toolItem);
    
        return hwndTT;
    }
    
    LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_INITDIALOG:
        {
            g_hwndTrackingTT = CreateTrackingToolTip(0, hDlg, L"");
            g_hwndTrackingTT1 = CreateTrackingToolTip(0, hDlg, L"");
            break;
        }
    
        case WM_MOUSELEAVE:
            SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem);
            SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem);
            g_TrackingMouse = FALSE;
            return FALSE;
    
        case WM_MOUSEMOVE:
            static int oldX, oldY;
            int newX, newY;
    
            if (!g_TrackingMouse)   // The mouse has just entered the window.
            {                       // Request notification when the mouse leaves.
    
                TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) };
                tme.hwndTrack = hDlg;
                tme.dwFlags = TME_LEAVE;
    
                TrackMouseEvent(&tme);
    
                // Activate the tooltip.
                SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem);
                SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem);
    
                g_TrackingMouse = TRUE;
            }
    
            newX = GET_X_LPARAM(lParam);
            newY = GET_Y_LPARAM(lParam);
    
            // Make sure the mouse has actually moved. The presence of the tooltip 
            // causes Windows to send the message continuously.
    
            if ((newX != oldX) || (newY != oldY))
            {
                oldX = newX;
                oldY = newY;
    
                // Update the text.
                WCHAR coords[12];
                wsprintf(coords, TEXT("%d, %d"), newX, newY);
    
                g_toolItem.lpszText = coords;
                SendMessage(g_hwndTrackingTT, TTM_SETTOOLINFO, 0, (LPARAM)&g_toolItem);
                SendMessage(g_hwndTrackingTT1, TTM_SETTOOLINFO, 0, (LPARAM)&g_toolItem);
    
                // Position the tooltip. The coordinates are adjusted so that the tooltip does not overlap the mouse pointer.
    
                //POINT pt = { newX, newY };
                POINT pt = { 50, 50 };
                ClientToScreen(hDlg, &pt);
                SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20));
                SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 100, pt.y - 20));
            }
            return FALSE;
        case WM_CLOSE:
            EndDialog(hDlg, FALSE);
            break;
        }
        return FALSE;
    }

    运行结果,

    可以自行设定ToolTips的位置TTM_TRACKPOSITION,修改ToolTips的显示值TTM_SETTOOLINFO, 控制Tooltips的显示TTM_TRACKACTIVE.

     

    3. 显示多行文本的ToolTips

    多行文本ToolTips参考图

    使用TTM_SETMAXTIPWIDTH 消息来创建一个多行文本的ToolTips。设置每行的宽度,超过此宽度的文本会自动换行。也可以使用 强制换行。

    注意NMTTDISPINFO 的szText成员最多只能存储80个字符。如果要显示长字符串,请用NMTTDISPINFO的lpszText指向一个长文本的字符。

    一下例子使用了TTN_GETDISPINFO通知码来修改tooltips的文本。

        case WM_NOTIFY:
        {
            switch (((LPNMHDR)lParam)->code)
            {
            case TTN_GETDISPINFO:
                LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lParam;
                SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 150);
                wcscpy_s(pInfo->szText, ARRAYSIZE(pInfo->szText), 
                    L"This
    is a very long text string " 
                    L"that must be broken into several lines.");
                break;
            }
            break;
        }

    测试用例

    显示两个固定位置的多行提示框 若窗口处于非激活状态则隐藏。

    提示框的位置会随着窗的移动而移动。

    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include <stdio.h>
    #include "resource.h"
    #pragma comment(lib, "comctl32.lib")
    
    LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);
    
    HINSTANCE g_hInst;
    HWND hTTWnd;
    HWND g_hwndTrackingTT;
    HWND g_hwndTrackingTT1;
    TOOLINFO g_toolItem;
    BOOL g_TrackingMouse = FALSE;
    
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, int)
    {
        g_hInst = hInstance;
        INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES };
        BOOL ret = InitCommonControlsEx(&cx);
        return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc);
    }
    
    
    // Description:
    //   Creates a tooltip for an item in a dialog box. 
    // Parameters:
    //   idTool - identifier of an dialog box item.
    //   nDlg - window handle of the dialog box.
    //   pszText - string to use as the tooltip text.
    // Returns:
    //   The handle to the tooltip.
    //
    HWND CreateToolTip(int toolID, HWND hDlg, PTSTR pszText)
    {
        if (!toolID || !hDlg || !pszText)
        {
            return FALSE;
        }
        // Get the window of the tool.
        HWND hwndTool = GetDlgItem(hDlg, toolID);
    
        // Create the tooltip. g_hInst is the global instance handle.
        HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
            CW_USEDEFAULT, CW_USEDEFAULT,
            CW_USEDEFAULT, CW_USEDEFAULT,
            hDlg, NULL,
            g_hInst, NULL);
    
        if (!hwndTool || !hwndTip)
        {
            return (HWND)NULL;
        }
    
        SetWindowPos(hwndTip, HWND_TOPMOST, 0, 0, 0, 0,
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
        // Associate the tooltip with the tool.
        TOOLINFO toolInfo = { 0 };
        toolInfo.cbSize = sizeof(toolInfo);
        toolInfo.hwnd = hDlg;
        toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
        toolInfo.uId = (UINT_PTR)hwndTool;
        toolInfo.lpszText = LPSTR_TEXTCALLBACK; // pszText;
        SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
    
        return hwndTip;
    }
    
    void CreateToolTipForRect(HWND hwndParent)
    {
        // Create a tooltip.
        HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            hwndParent, NULL, g_hInst, NULL);
    
        SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0,
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
    
        // Set up "tool" information. In this case, the "tool" is the entire parent window.
    
        TOOLINFO ti = { 0 };
        ti.cbSize = sizeof(TOOLINFO);
        ti.uFlags = TTF_SUBCLASS;
        ti.hwnd = hwndParent;
        ti.hinst = g_hInst;
        ti.lpszText = TEXT("This is your tooltip string.");
    
        GetClientRect(hwndParent, &ti.rect);
    
        // Associate the tooltip with the "tool" window.
        SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
    }
    
    HWND CreateTrackingToolTip(int toolID, HWND hDlg, WCHAR* pText)
    {
        // Create a tooltip.
        HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            hDlg, NULL, g_hInst, NULL);
    
        if (!hwndTT)
        {
            return NULL;
        }
    
        // Set up the tool information. In this case, the "tool" is the entire parent window.
    
        g_toolItem.cbSize = sizeof(TOOLINFO);
        g_toolItem.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE;
        g_toolItem.hwnd = hDlg;
        g_toolItem.hinst = g_hInst;
        g_toolItem.lpszText = LPSTR_TEXTCALLBACK;//pText;
        g_toolItem.uId = (UINT_PTR)hDlg;
    
        GetClientRect(hDlg, &g_toolItem.rect);
    
        // Associate the tooltip with the tool window.
    
        SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&g_toolItem);
        SendMessage(hwndTT, TTM_SETDELAYTIME, (WPARAM)TTDT_AUTOPOP, (LPARAM)MAKELONG(30 * 1000, 0));
    
        return hwndTT;
    }
    
    LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        static BOOL bActive = FALSE;
        switch (uMsg)
        {
        case WM_INITDIALOG:
        {
            g_hwndTrackingTT = CreateTrackingToolTip(0, hDlg, L"");
            g_hwndTrackingTT1 = CreateTrackingToolTip(0, hDlg, L"");
            //hTTWnd = CreateToolTip(IDCANCEL, hDlg, TEXT("IDCANCEL String 
     nextline"));
            //CreateToolTipForRect(hDlg);
    
            break;
        }
    
        case WM_NOTIFY:
        {
            switch (((LPNMHDR)lParam)->code)
            {
            case TTN_GETDISPINFO: /*TTN_NEEDTEXT:*/
                LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lParam;
                SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 150);
                wcscpy_s(pInfo->szText, ARRAYSIZE(pInfo->szText),
                    L"This
    is a very long text string " 
                    L"that must be broken into several lines.");
                break;
            }
    
            break;
        }
        case WM_MOVE:
        {
            POINT pt = { 50, 50 };
            ClientToScreen(hDlg, &pt);
            SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20));
            SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y + 80));
        }
    
    
            break;
        case WM_ACTIVATE:
            // if the main windows is inactive ,disappear the tooltips.
            if (LOWORD(wParam) == WA_INACTIVE)
            {
                SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem);
                SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem);
                bActive = FALSE;
            }
            else
            {
                SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem);
                SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem);
                POINT pt = { 50, 50 };
                ClientToScreen(hDlg, &pt);
                SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20));
                SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y + 80));
                bActive = TRUE;
            }
    
            break;
    
        case WM_MOUSEMOVE:
        {
            if (!bActive)
                break;
    
            SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem);
            SendMessage(g_hwndTrackingTT1, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&g_toolItem);
    
            POINT pt = { 50, 50 };
            ClientToScreen(hDlg, &pt);
            SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20));
            SendMessage(g_hwndTrackingTT1, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x + 10, pt.y + 80));
    
        }
            break;
        case WM_CLOSE:
            EndDialog(hDlg, FALSE);
            break;
        }
        return FALSE;
    }

    熟悉了ToolTips的用法可以对其用C++做一个封装方便调用。

    自己实现了一个可以自行设定位置的基于Windows ToolTips的类。并且使用了SetWindowsLongPtr自行处理了WM_NOTIFY消息。(主窗口不必关心内部消息处理)

    CToolTips.h  头文件

    /*                    CToolTips - CToolTips.h
    *
    *        Author: Sesiria <stjohnson_free@hotmail.com>
    *        Copyright (c) 2017 Sesiria.
    *
    *    CToolTips module header file
    *    This module is designed to to encapsulation the behavior of the standard Windows ToolTips control.
    */
    #ifndef _CTOOLTIPS_H_
    #define _CTOOLTIPS_H_
    
    #include <windows.h>
    #include <CommCtrl.h>
    #include <map>
    #include <list>
    #include <string>
    
    
    
    class CToolTips  //Based on the Track style of the ToolTips control.
    {
        // member function.
    public:
        CToolTips(HWND hParentWnd, HINSTANCE hInstance, bool MultiLine = false); //Normal Style and Multiline
        ~CToolTips();
    
        void setText(LPCTSTR szText);
        void setMultiLineText(LPCTSTR szText, const LONG nWidth);
        void initToolTips();
        void setPosition(const POINT& pt);
        void setVisible(bool bVisible);
        void setUpdate();
    
        static LRESULT CALLBACK tooltipWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
        static LRESULT CALLBACK parentWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
        WNDPROC getParentWndProc()
        {
            return m_parentWndProc;
        };
    
    
    private:
        //
        void setActive(bool bActive);
        void _registerWndProc();
        void _unregisterWndProc();
    
        bool _createToolTips();
        void _destroyToolTips();
        void _addtoInstanceTable();
        void _removeFromInstanceTable();
    
        // member variable.
    private:
        LPTSTR m_szText;
        LPTSTR m_multiText;
        LONG m_nWidth;
        bool m_bMultiLine;
        bool m_bActive;
        bool m_bVisible;
        POINT m_pos;
    
        HWND m_hParent;
        HWND m_hToolTips;
        HINSTANCE m_hInst;
        TOOLINFO m_toolInfo;
    
        WNDPROC m_parentWndProc;
        WNDPROC m_tooltipWndProc;
    
    };
    
    #endif // _CTOOLTIPS_H_

    CToolTips.cpp 源文件

    /*                    CToolTips - CToolTips.cpp
    *
    *        Author: Sesiria <stjohnson_free@hotmail.com>
    *        Copyright (c) 2017 Sesiria.
    *
    *    CToolTips module source file
    *    This module is designed to to encapsulation the behavior of the standard Windows ToolTips control.
    */
    #include "CToolTips.h"
    
    #define DELETEP(p)      do { if (p) { delete(p); (p)=NULL; } } while (0)
    #define DELETEPV(pa)    do { if (pa) { delete [] (pa); (pa)=NULL; } } while (0)
    
    typedef std::list<HWND> ListInstance;
    
    // this data struct is used to support different parent dialog bind with the tooltips instance.
    // the HWND is the parent dialog HWND
    // ListInstance is a list container to store all the handle of the tooltips relative the same
    // parent dialog.
    typedef std::map<HWND, ListInstance*> TableInstance;
    
    /////////////////////////////////////////////////////////////
    
    static TableInstance g_tblInstance;
    
    bool isInTable(HWND hParent)
    {
        TableInstance::iterator iter = g_tblInstance.find(hParent);
        if (iter == g_tblInstance.end())
            return false;
        return true;
    }
    
    bool isInTable(HWND hParent, HWND hToolTips)
    {
        ListInstance * pList = NULL;
        TableInstance::iterator iter = g_tblInstance.find(hParent);
        if (iter == g_tblInstance.end()) // the parent window has not been register.
        {
            return false;
        }
        else // the parent windows has been registered we just get the parent wndproc from the other nodes.
        {
            pList = iter->second;
            HWND hToolTips = *pList->begin();
            ListInstance::const_iterator iterList = std::find(pList->begin(), pList->end(), hToolTips);
            if (iterList == pList->end())
                return false;
        }
        return true;
    }
    
    HWND getFirstToolTips(HWND hParent)
    {
        if (!isInTable(hParent))
            return NULL;
        ListInstance * pList = NULL;
        TableInstance::iterator iter = g_tblInstance.find(hParent);
    
        return *iter->second->begin();
    }
    
    CToolTips::CToolTips(HWND hParentWnd, HINSTANCE hInstance, bool MultiLine /*= false*/)
        :m_szText(NULL),
        m_multiText(NULL),
        m_nWidth(0),
        m_bMultiLine(MultiLine),
        m_bActive(false),
        m_bVisible(false),
        m_hParent(hParentWnd),
        m_hToolTips(NULL),
        m_parentWndProc(NULL),
        m_hInst(hInstance)
    {
        m_pos.x = 0;
        m_pos.y = 0;
        memset(&m_toolInfo, 0, sizeof(TOOLINFO));
    
        if (_createToolTips())
        {
            _registerWndProc();
            _addtoInstanceTable();
        }
    }
    
    CToolTips::~CToolTips()
    {
        _removeFromInstanceTable();
        _unregisterWndProc();
        if (m_hToolTips)
        {
            DestroyWindow(m_hToolTips);
            m_hToolTips = NULL;
        }
    
        DELETEPV(m_szText);
        DELETEPV(m_multiText);
    }
    
    bool CToolTips::_createToolTips()
    {
        if (!m_hParent || !m_hInst)
            return false;
    
        // Create the Handle for the ToolTips control
        m_hToolTips = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            m_hParent, NULL, m_hInst, NULL);
    
        return (m_hToolTips != NULL);
    }
    
    void CToolTips::initToolTips()
    {
        if (!m_hToolTips || !m_hInst)
            return;
    
        SetWindowPos(m_hToolTips, HWND_TOPMOST, 0, 0, 0, 0,
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
    
        //Init the TOOLINFO
        m_toolInfo.cbSize = sizeof(TOOLINFO);
        m_toolInfo.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE;
        m_toolInfo.hwnd = m_hParent;
        m_toolInfo.hinst = m_hInst;
        m_toolInfo.lpszText = LPSTR_TEXTCALLBACK;
        m_toolInfo.uId = (UINT_PTR)m_hParent;
    
        // Associate the tooltip with the tool window.
        SendMessage(m_hToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&m_toolInfo);
    
        // Set the DelayTime of the ToolTips
        SendMessage(m_hToolTips, TTM_SETDELAYTIME, (WPARAM)TTDT_AUTOPOP, (LPARAM)MAKELONG(30 * 1000, 0));
    
        // By default, we just set the tooltips to inactive.
        SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&m_toolInfo);
    
        m_bActive = true;
    }
    
    void CToolTips::_addtoInstanceTable()
    {
        ListInstance * pList = NULL;
        TableInstance::iterator iter = g_tblInstance.find(m_hParent);
        if (iter == g_tblInstance.end()) // the parent window has not been register.
        {
            pList = new ListInstance;
            pList->push_back(m_hToolTips);
            g_tblInstance.insert(std::make_pair(m_hParent, pList));
        }
        else
        {
            pList = iter->second;
            ListInstance::const_iterator iterSet = std::find(pList->begin(), pList->end(), m_hToolTips);
            if (iterSet == pList->end())
                pList->push_back(m_hToolTips);
        }
        
    }
    
    void CToolTips::_removeFromInstanceTable()
    {
        TableInstance::iterator iter = g_tblInstance.find(m_hParent);
        if (iter == g_tblInstance.end())
            return;
    
        ListInstance * pSet = iter->second;
        pSet->remove(m_hToolTips);
    
    }
    
    
    
    void CToolTips::_registerWndProc()
    {
        // bind the this pointer to the handle of the tooltips
        SetWindowLongPtr(m_hToolTips, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)); 
    
        // Register the windows proc for the tooltip dialog.
        m_tooltipWndProc = (WNDPROC)SetWindowLongPtr(m_hToolTips,
            GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&CToolTips::tooltipWndProc));
    
        // Register the windows proc for the parents dialog.
        if (!isInTable(m_hParent) && !m_parentWndProc)
        {
            m_parentWndProc = (WNDPROC)SetWindowLongPtr(m_hParent,
                GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&CToolTips::parentWndProc));
        }
        else
        {
            HWND hToolTips;
            if (!(hToolTips = getFirstToolTips(m_hParent)))
                return;
            LONG_PTR user_data = GetWindowLongPtr(hToolTips, GWLP_USERDATA);
            CToolTips *pToolTips = reinterpret_cast<CToolTips*>(user_data);
            m_parentWndProc = pToolTips->getParentWndProc();
        }
    }
    
    void CToolTips::_unregisterWndProc()
    {
        // if it is the last element relative to the parent dialog just unregister the wndproc.
        TableInstance::iterator iter = g_tblInstance.find(m_hParent);
        if (iter != g_tblInstance.end() && m_parentWndProc != NULL)
        {
            ListInstance *pSet = iter->second;
            if (pSet->size() == 0)// it is the empty set.
            {
                (WNDPROC)SetWindowLongPtr(m_hParent,
                    GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_parentWndProc));
    
                g_tblInstance.erase(iter);
                DELETEP(pSet);
            }
            m_parentWndProc = NULL;
        }
    
        // unregister the window procedure and restore to the default procedure.
        if (m_tooltipWndProc)
        {
            SetWindowLongPtr(m_hToolTips,
                GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_tooltipWndProc));
            m_tooltipWndProc = NULL;
        }
    
        // unregister the this pointer to the hwnd GWL_USERDATA 
        SetWindowLongPtr(m_hToolTips, GWLP_USERDATA, NULL);
    }
    
    LRESULT CALLBACK CToolTips::tooltipWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
    {
        LONG_PTR user_data = GetWindowLongPtr(hWnd, GWLP_USERDATA);
        CToolTips *this_window = reinterpret_cast<CToolTips*>(user_data);
        if (!this_window || !this_window->m_tooltipWndProc)
            return DefWindowProc(hWnd, Msg, wParam, lParam);
    
        static bool g_TrackingMouse = false;
    
        switch (Msg)
        {
        case WM_MOUSELEAVE:
            g_TrackingMouse = false;
            return DefWindowProc(hWnd, Msg, wParam, lParam);
            break;
        case WM_MOUSEMOVE:
            if (!g_TrackingMouse)
            {
                TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) };
                tme.hwndTrack = hWnd;
                tme.dwFlags = TME_LEAVE;
                TrackMouseEvent(&tme);
                this_window->setUpdate();
                g_TrackingMouse = true;
            }
            break;
        }
    
        return CallWindowProc(this_window->m_tooltipWndProc, hWnd, Msg, wParam, lParam);
    }
    
    // hook for the parent window procedure
    LRESULT CALLBACK CToolTips::parentWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
    {
        LONG_PTR user_data = GetWindowLongPtr(getFirstToolTips(hWnd), GWLP_USERDATA);
        CToolTips *this_window = reinterpret_cast<CToolTips*>(user_data);
    
        if (!this_window || !this_window->getParentWndProc())
            return DefWindowProcW(hWnd, Msg, wParam, lParam);
    
        switch (Msg)
        {
        case WM_NOTIFY:
            {
                switch (((LPNMHDR)lParam)->code)
                {
                case TTN_GETDISPINFO: /*TTN_NEEDTEXT:*/
                    LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lParam;
                    SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, this_window->m_nWidth);
                    lstrcpyn(pInfo->szText, this_window->m_multiText, ARRAYSIZE(pInfo->szText));
                    break;
                }
            }
            break;
    
        case WM_MOVE:
            {
                TableInstance::iterator iter = g_tblInstance.find(hWnd);
                if (iter == g_tblInstance.end())
                    break;
                ListInstance *pList = iter->second;
                ListInstance::iterator  iterlist;
                for (iterlist = pList->begin(); iterlist != pList->end(); ++iterlist)
                {
                    LONG_PTR user_data = GetWindowLongPtr((HWND)(*iterlist), GWLP_USERDATA);
                    CToolTips *tooltips_window = reinterpret_cast<CToolTips*>(user_data);
                    if (!tooltips_window)
                        continue;
                    tooltips_window->setPosition(tooltips_window->m_pos);
                }        
            }
            break;
    
        case WM_ACTIVATE:
        {
            TableInstance::iterator iter = g_tblInstance.find(hWnd);
            if (iter == g_tblInstance.end())
                break;
            ListInstance *pList = iter->second;
            ListInstance::iterator  iterlist;
            for (iterlist = pList->begin(); iterlist != pList->end(); ++iterlist)
            {
                LONG_PTR user_data = GetWindowLongPtr((HWND)(*iterlist), GWLP_USERDATA);
                CToolTips *tooltips_window = reinterpret_cast<CToolTips*>(user_data);
                if (!tooltips_window)
                    continue;
                if (LOWORD(wParam) == WA_INACTIVE)
                {
                    tooltips_window->setActive(false);
                }
                else
                {
                    tooltips_window->setActive(true);
                }
            }
        }
        }
    
        return CallWindowProc(this_window->m_parentWndProc, hWnd, Msg, wParam, lParam);
    }
    
    void CToolTips::setText(LPCTSTR szText)
    {
        DELETEPV(m_szText);
        m_szText = new TCHAR[lstrlen(szText) + 1];
        lstrcpy(m_szText, szText);
    }
    
    void CToolTips::setMultiLineText(LPCTSTR szText, const LONG nWidth)
    {
        DELETEPV(m_multiText);
        m_multiText = new TCHAR[lstrlen(szText) + 1];
        lstrcpy(m_multiText, szText);
    
        m_nWidth = nWidth;
        if (m_bActive)
        {
            setUpdate();
        }
    }
    
    void CToolTips::setPosition(const POINT& pt)
    {
        m_pos.x = pt.x;
        m_pos.y = pt.y;
        POINT newPt = { m_pos.x, m_pos.y};
        ClientToScreen(m_hParent, &newPt);
        SendMessage(m_hToolTips, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(newPt.x, newPt.y));
    }
    
    void CToolTips::setActive(bool bActive)
    {
        m_bActive = bActive;
        if (m_bActive && m_bVisible)
        {
            SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&m_toolInfo);
            setPosition(m_pos);
        }
        else
        {
            SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&m_toolInfo);
        }    
    }
    
    void CToolTips::setVisible(bool bVisible)
    {
        m_bVisible = bVisible;
        if (m_bVisible && m_bActive)
        {
            SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&m_toolInfo);
            setPosition(m_pos);
        }
        else
        {
            SendMessage(m_hToolTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&m_toolInfo);
        }
    }
    
    
    void CToolTips::setUpdate()
    {
        SendMessage(m_hToolTips, TTM_UPDATE, 0, 0);
    }

    main.cpp 测试代码

    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include <stdio.h>
    #include <assert.h>
    #include "resource.h"
    #include "CToolTips.h"
    #pragma comment(lib, "comctl32.lib")
    
    
    #define MULTILINE_TEXT    TEXT("This
    is a very long text string that must be broken into several lines.")
    #define MULTILINE_TEXT_TIME    TEXT("This
    is a very long text string that must be broken into several lines. %d")
    
    LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);
    LRESULT CALLBACK MyDlgProc1(HWND, UINT, WPARAM, LPARAM);
    
    HINSTANCE g_hInst;
    CToolTips *g_pToolTips = NULL;
    CToolTips *g_pToolTips1 = NULL;
    
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, int)
    {
        g_hInst = hInstance;
        INITCOMMONCONTROLSEX cx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES };
        BOOL ret = InitCommonControlsEx(&cx);
        return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc1);
    }
    
    void TEST_Constructor(HWND hDlg)
    {
        g_pToolTips = new CToolTips(hDlg, g_hInst, true);
        assert(g_pToolTips != NULL);
        g_pToolTips1 = new CToolTips(hDlg, g_hInst, true);
    }
    
    void TEST_Destructor()
    {
        if (g_pToolTips)
        {
            delete g_pToolTips;
            g_pToolTips = NULL;
        }
    
        if (g_pToolTips1)
        {
            delete g_pToolTips1;
            g_pToolTips1 = NULL;
        }
    }
    
    void TEST_MultilineToolTips()
    {
        if (!g_pToolTips)
            return;
        g_pToolTips->setMultiLineText(MULTILINE_TEXT, 150);
        g_pToolTips->initToolTips();
        POINT pt = { 50, 50 };
        g_pToolTips->setPosition(pt);
        g_pToolTips->setVisible(true);
    
        if (!g_pToolTips1)
            return;
        g_pToolTips1->setMultiLineText(MULTILINE_TEXT, 150);
        g_pToolTips1->initToolTips();
        POINT pt1 = { 100, 50 };
        g_pToolTips1->setPosition(pt1);
        g_pToolTips1->setVisible(true);
    }
    
    void TEST_StartDynamicUpdate(HWND HDlg)
    {
        SetTimer(HDlg, 1, 5000, NULL);
    }
    void TEST_DynamicUpdateToolTips(HWND hDlg)
    {
        TCHAR buf[255] = { 0 };
        wsprintf(buf, MULTILINE_TEXT_TIME, GetCurrentTime());
        //
        //
        static int i = 0;
        if (i % 2 == 0)
            g_pToolTips1->setMultiLineText(buf, 150);
        else
            g_pToolTips->setMultiLineText(buf, 150);
        i++;
    }
    
    void TEST_KillDynamicUpdate(HWND hDlg)
    {
        KillTimer(hDlg, 1);
    }
    
    LRESULT CALLBACK MyDlgProc1(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        static CToolTips * pToolTips;
        switch (uMsg)
        {
        case WM_INITDIALOG:
            TEST_Constructor(hDlg);
            TEST_MultilineToolTips();
            TEST_StartDynamicUpdate(hDlg);
            break;
        case WM_TIMER:
            TEST_DynamicUpdateToolTips(hDlg);
            break;
    
        case WM_CLOSE:
            TEST_KillDynamicUpdate(hDlg);
            TEST_Destructor();
            EndDialog(hDlg, FALSE);
            break;
        }
        return FALSE;
    
    }

    运行结果如下

    创建了两个多行气泡的ToolTips,  定时器每间隔5秒会更新气泡的内容,气泡的位置会随着窗口被拖动而自行 调整, 当窗口处于非激活状态时候气泡自动隐藏。

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

  • 相关阅读:
    Tensor总结
    Tensorflow池化
    conda操作
    KS值计算
    supervisor实践
    npm/yarn实践
    nni 环境搭建
    阿里云个人邮箱配置
    Jinja2宏使用
    利用VS code 远程调试 docker 中的 dotnet 应用
  • 原文地址:https://www.cnblogs.com/happykoukou/p/9110813.html
Copyright © 2011-2022 走看看