为什么我的程序在按钮上没有显示工具提示?

编程语言 2026-07-09

我需要创建一个带有工具提示的按钮。程序会显示按钮,但不会显示工具提示。

我已经创建了一个用于演示问题的最小可复现示例(MRE)。按钮和工具提示是在一个 CreateButton() 函数中创建的。该程序是一个没有资源的WinAPI应用程序。

问题出在哪里?

#pragma once

#define WIN32_LEAN_AND_MEAN   

#include <windows.h>

#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include "assert.h"
#include "commctrl.h"

#pragma comment( lib, "comctl32" )


#define MAX_LOADSTRING 100
#define ID_MY_BUTTON1   11

HINSTANCE hInst; 
WCHAR szTitle[MAX_LOADSTRING] = L"Window title";
WCHAR szWindowClass[MAX_LOADSTRING] = L"WindowClass";


ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);


int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    INITCOMMONCONTROLSEX icex;
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC = ICC_BAR_CLASSES | ICC_TAB_CLASSES;
    bool ok = InitCommonControlsEx(&icex);
    assert(ok == TRUE);

    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    MyRegisterClass(hInstance);

    if (!InitInstance(hInstance, nCmdShow))
        return FALSE;

    MSG msg;

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}



ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = NULL;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = NULL;

    return RegisterClassExW(&wcex);
}



bool CreateButton(HINSTANCE hInstance, HWND hMainWnd) {

    bool ok = true;
    int StartX = 0;
    const int ButtonWidth = 120;
    RECT rect;

    HMENU ButtonID = (HMENU) 1;
    HWND hButton = CreateWindow(L"BUTTON", L"My button 1",
        WS_VISIBLE | WS_CHILD,
        StartX, 0, ButtonWidth, 30,
        hMainWnd, ButtonID, hInst, NULL);

    // Create the tooltip. g_hInst is the global instance handle.
    HWND hwndTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
        TTS_ALWAYSTIP,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        hButton, NULL,
        hInstance, NULL);
    assert(hwndTip != NULL);

    // Associate the tooltip with the tool.
    TOOLINFO toolInfo = { 0 };
    toolInfo.cbSize = sizeof(toolInfo);
    toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;

    toolInfo.hwnd = hMainWnd;
    toolInfo.uId = (UINT_PTR) hButton;

    toolInfo.lpszText = (LPWSTR)L"tool tip";

    toolInfo.hinst = hInstance;

    auto res = SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);


    return true;

}



BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance;

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

    assert(hWnd != NULL);

    CreateButton(hInst, hWnd);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Разобрать выбор в меню:
        switch (wmId) {
        case ID_MY_BUTTON1: {
            break;
        }
                          */
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);

        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

解决方案

简要结论:附加一个 应用程序清单,声明对Common Controls v6程序集的依赖。同时,最好声明 DPI感知系统功能兼容性UAC(用户账户控制)合规性 等。

详细回答:程序之所以不显示工具提示,是因为发送 TTM_ADDTOOL 消息返回了 FALSE,表示失败。这个失败的原因是因为 TOOLINFO::cbSize 字段填写不正确。处理公共控件时要记住底层实现至少有四种变体:ANSI与 UNICODE、DLL版本5与 DLL版本6。所讨论的代码显然是为UNICODE变体定制的,在没有清单的情况下它将使用DLL版本5。关键在于不同DLL版本对结构体的大小有些差异:[不同公共控件版本的结构体大小] 的说明。仅仅包含Windows头文件就会将 TOOLINFO 结构声明为最大大小,对应于可以被DLL版本6处理的 TTTOOLINFOW_V3_SIZE 值。ANSI的 DLL版本5能接受比预期更大的结构体大小,而UNICODE的 DLL版本5会拒绝它(这实际上可能是大小检查中的一个bug)。

为了让两者都“满意”,可以使用 TTTOOLINFOW_V2_SIZE 值。不过这会产生Win95风格的工具提示。

Common Controls V5 tooltip

更好的做法是使用 TTTOOLINFOW_V3_SIZE 值(应该等于 sizeof(toolInfo))并声明对DLL v6的依赖。这将产生看起来更现代的控件。

Common Controls V6 tooltip

可以同时以ANSI和 UNICODE构建的MCVE,取消注释 COMM_CTRL_V6 以使用DLL v6:

#define WIN32_LEAN_AND_MEAN
#include <tchar.h>
#include <Windows.h>
#include <CommCtrl.h>

#pragma comment(lib, "comctl32")
//#define COMM_CTRL_V6
#ifdef COMM_CTRL_V6
#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#error Unknown platform
#endif
#endif

LRESULT CALLBACK
WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY: PostQuitMessage(0); return 0;
        default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
}

int APIENTRY
#ifdef _UNICODE
wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
#else
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
#endif
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    INITCOMMONCONTROLSEX icex = {};
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC = ICC_BAR_CLASSES;
    if (FALSE == InitCommonControlsEx(&icex))
    {
        return 1;
    }

    WNDCLASSEX wcex = {};
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = NULL;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = _T("WindowClass");
    wcex.hIconSm = NULL;

    if (ATOM(0) == RegisterClassEx(&wcex))
    {
        return 2;
    }

    HWND hWnd = CreateWindow(wcex.lpszClassName, _T("Window title"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    if (HWND() == hWnd)
    {
        return 3;
    }

    HWND hButton = CreateWindow(WC_BUTTON, _T("My button 1"),
        WS_VISIBLE | WS_CHILD,
        0, 0, 120, 30,
        hWnd, (HMENU) 11, hInstance, NULL);
    if (HWND(0) == hButton)
    {
        return 4;
    }

    // Create the tooltip. g_hInst is the global instance handle.
    HWND hwndTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
        WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        hWnd, NULL,
        hInstance, NULL);
    if (HWND(0) == hwndTip)
    {
        return 5;
    }

    // Associate the tooltip with the tool.
    TOOLINFO toolInfo = {};
#ifdef COMM_CTRL_V6
    toolInfo.cbSize = TTTOOLINFOW_V3_SIZE;
    static_assert(TTTOOLINFOW_V3_SIZE <= sizeof(toolInfo));
#else
    toolInfo.cbSize = TTTOOLINFOW_V2_SIZE;
    static_assert(TTTOOLINFOW_V2_SIZE <= sizeof(toolInfo));
#endif
    // toolInfo.cbSize = sizeof(toolInfo);
    toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
    toolInfo.hwnd = hWnd;
    toolInfo.uId = (UINT_PTR) hButton;
    toolInfo.lpszText = const_cast<decltype(toolInfo.lpszText)>(_T("tool tip"));
    if (FALSE == SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo))
    {
        return 6;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if (FALSE == UnregisterClass(wcex.lpszClassName, hInstance))
    {
        return 7;
    }
    return (int)msg.wParam;
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章