如何在选项卡控件区域内添加控件?

How to add controls within tab control area?

如何获取选项卡控件区域坐标然后在该区域添加按钮?我不知道我应该使用哪个 TabCtrl_GetItemRect(), GetWindowRect() 或其他东西,因为我似乎没有得到我想要的坐标,这是选项卡控制区域,我可以在其中放置按钮。它目前位于远离选项卡控件白色区域的位置,我想将它们放置在那里,如下图所示。将它们放入此选项卡控件中的正确方法是什么?

处理这个的函数是这样的:

void CreateButtons(HWND hwnd)
{
    RECT rt = {0};
    //TabCtrl_GetItemRect(hTab, 0, &rt);
    GetWindowRect(hTab, &rt);
    //GetClientRect(hTab, &rt);
    /*
    wchar_t buffer[256] = {0};
    wsprintf(buffer, 
        L"top = %d, bottom = %d, left = %d, right = %d",
        rt.top, rt.bottom, rt.left, rt.right);
    MessageBox(NULL, buffer, L"", MB_OK);
    */
    int id = 4;
    static const wchar_t *title[] = { L"Button A", L"Button B", L"Button C",
                                      L"Button D", L"Button E", L"Button F",
                                      L"Button G" , L"Button 001", L"Button 002",
                                      L"Button 003", L"Button 004" };
    const int nMaxButtonPerRow = 3;
    const int cx_margin = 10;
    const int cy_bottomMarge = 10;
    const int cy_breakSize = 25;
    int cx_initPos = (int)rt.left;
    int cx = cx_initPos;
    int cy = 50;
    const int width = 80;
    const int height = 25;
    for(int i = 0; i < sizeof(title)/sizeof(title[0]); ++i)
    {
        if(i != 0 && (i % nMaxButtonPerRow) == 0) {
            cy += cy_breakSize + cy_bottomMarge;
            cx = cx_initPos;
        }

        CreateWindow(L"button", title[i], 
                    WS_VISIBLE | WS_CHILD | WS_TABSTOP,
                    cx, 
                    cy,
                    width,
                    height,
                    hwnd, (HMENU) id++, NULL, NULL);
        cx += width + cx_margin;
    }
}

完整代码:

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")

#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE

#include <windows.h>
#include <Commctrl.h>
#include <crtdbg.h>
#include <strsafe.h>
#include <string.h>
#include <assert.h>

#ifdef UNICODE
#define STRSPLIT wcsrchr
#else
#define STRSPLIT strrchr
#endif

#define __FILENAME__ (STRSPLIT(TEXT(__FILE__), '/') ? STRSPLIT(TEXT(__FILE__), '/') + 1 : TEXT(__FILE__))

#define NAMEOF(s) TEXT(#s)

#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK CreateTabProc(HWND, UINT, WPARAM, LPARAM);
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void CreateTab(HWND hwnd);
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text);
void CreateButtons(HWND hwnd);

HINSTANCE ghInstance;
HWND hTab;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR pCmdLine, int nCmdShow)
{

    MSG  msg = {0};
    HWND hwnd;
    WNDCLASSW wc = {0};

    wc.lpszClassName = L"Window";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    
    InitComControls();
    if(!RegisterClass(&wc)) {
        ErrorExit(NAMEOF(RegisterClass), __LINE__, __FILENAME__);
    }

    int width = 500;
    int height = 350;
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);
    int cx = (screenWidth - width) / 2;
    int cy = (screenHeight - height) / 2;
    hwnd = CreateWindowW(wc.lpszClassName, L"Window",
                        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                        cx, cy, width, height, NULL, NULL, 
                        hInstance, NULL);
    ghInstance = hInstance;

    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!IsDialogMessage(hwnd, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

  switch(msg)
  {
      case WM_CREATE:
        CreateWindowW(L"Static", L"This is label 1...",
          WS_VISIBLE | WS_CHILD | WS_TABSTOP,
          50, 10, 130, 25, hwnd, (HMENU) 18, NULL, NULL);
        CreateWindowW(L"Static", L"This is label 2...",
          WS_VISIBLE | WS_CHILD | WS_TABSTOP,
          50, 40, 130, 25, hwnd, (HMENU) 19, NULL, NULL);
          CreateTab(hwnd);
          CreateButtons(hwnd);
          //ChangeToDefaultFont(hwnd);
      break;

      case WM_DESTROY:
          PostQuitMessage(0);
          return 0;
  }

  return DefWindowProc(hwnd, msg, wParam, lParam);
}

void CreateTab(HWND hwnd)
{
  hTab =
   CreateWindow(WC_TABCONTROLW, NULL,
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP | WS_EX_CONTROLPARENT,
            50, 80, 400, 250,
            hwnd,
            (HMENU) 1,
            NULL,
            NULL);
    InsertTabItem(hTab, 2, L"Tab 1");
    InsertTabItem(hTab, 3, L"Tab b");
}

void CreateButtons(HWND hwnd)
{
    RECT rt = {0};
    //TabCtrl_GetItemRect(hTab, 0, &rt);
    GetWindowRect(hTab, &rt);
    //GetClientRect(hTab, &rt);
    /*
    wchar_t buffer[256] = {0};
    wsprintf(buffer, 
        L"top = %d, bottom = %d, left = %d, right = %d",
        rt.top, rt.bottom, rt.left, rt.right);
    MessageBox(NULL, buffer, L"", MB_OK);
    */
    int id = 4;
    static const wchar_t *title[] = { L"Button A", L"Button B", L"Button C",
                                      L"Button D", L"Button E", L"Button F",
                                      L"Button G" , L"Button 001", L"Button 002",
                                      L"Button 003", L"Button 004" };
    const int nMaxButtonPerRow = 3;
    const int cx_margin = 10;
    const int cy_bottomMarge = 10;
    const int cy_breakSize = 25;
    int cx_initPos = (int)rt.left;
    int cx = cx_initPos;
    int cy = 50;
    const int width = 80;
    const int height = 25;
    for(int i = 0; i < sizeof(title)/sizeof(title[0]); ++i)
    {
        if(i != 0 && (i % nMaxButtonPerRow) == 0) {
            cy += cy_breakSize + cy_bottomMarge;
            cx = cx_initPos;
        }

        CreateWindow(L"button", title[i], 
                    WS_VISIBLE | WS_CHILD | WS_TABSTOP,
                    cx, 
                    cy,
                    width,
                    height,
                    hwnd, (HMENU) id++, NULL, NULL);
        cx += width + cx_margin;
    }
}

void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text)
{
    TCITEMW tci = {0};
    tci.mask = TCIF_TEXT;
    tci.pszText = text;
    tci.cchTextMax = lstrlenW(text);
    SendMessage(tabHwnd, TCM_INSERTITEMW, id, (LPARAM) &tci);
}

void InitComControls()
{
    INITCOMMONCONTROLSEX icex;
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC = ICC_TAB_CLASSES;
    InitCommonControlsEx(&icex);
}

void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename)
{
    DWORD dw = ShowLastError(lpszFunction, line, filename);
    ExitProcess(dw);
}

DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename)
{
    #define MAX_DIGITS 16

    DWORD dw = GetLastError();
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0,
        NULL
    );

    lpDisplayBuf = (LPVOID) LocalAlloc(LMEM_ZEROINIT, 
            (lstrlen((LPCTSTR)lpMsgBuf) +
            lstrlen((LPCTSTR)lpszFunction) + 40 +
            (line > 0 ? MAX_DIGITS : 0) +
            (filename != NULL ? lstrlen(filename) : 0)) *
            sizeof(TCHAR)
    );
    StringCchPrintf((LPTSTR)lpDisplayBuf,
                    LocalSize(lpDisplayBuf) / sizeof(TCHAR),
                    TEXT("%s failed with %d: %s"),
                    lpszFunction, dw, lpMsgBuf
    );
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    return dw;
}

更新 1: 设法用 GetWindowRect()MapWindowPoints() 做到了这一点。我错过了前一个。参见 this post。它获得了正确的选项卡控件坐标,但尚未获得控件应该位于的显示区域(白色区域)。我怎样才能得到这个白色区域的坐标?如果我设法获得选项卡按钮(由 TCM_INSERTITEM 消息插入的按钮)所在的 window 区域大小,也可以工作(不确定是否非常优雅)将在设置 cx_initPos值。

最新代码:

void CreateButtons(HWND hwnd)
{
    RECT rt = GetLocalCoordinates(hTab);

    int id = 4;
    static const wchar_t *title[] = { L"Button A", L"Button B", L"Button C",
                                      L"Button D", L"Button E", L"Button F",
                                      L"Button G" , L"Button 001", L"Button 002",
                                      L"Button 003", L"Button 004" };
    const int nMaxButtonPerRow = 3;
    const int cx_margin = 10;
    const int cy_bottomMarge = 10;
    const int cy_breakSize = 25;
    int cx_initPos = rt.left;
    int cx = cx_initPos;
    int cy = rt.top;
    const int width = 80;
    const int height = 25;
    for(int i = 0; i < sizeof(title)/sizeof(title[0]); ++i)
    {
        if(i != 0 && (i % nMaxButtonPerRow) == 0) {
            cy += cy_breakSize + cy_bottomMarge;
            cx = cx_initPos;
        }

        CreateWindow(L"button", title[i], 
                    WS_VISIBLE | WS_CHILD | WS_TABSTOP,
                    cx, 
                    cy,
                    width,
                    height,
                    hwnd, (HMENU) id++, NULL, NULL);
        cx += width + cx_margin;
    }
}

RECT GetLocalCoordinates(HWND hWnd)
{
    RECT Rect;
    GetWindowRect(hWnd, &Rect);
    MapWindowPoints(HWND_DESKTOP, GetParent(hWnd), (LPPOINT) &Rect, 2);
    return Rect;
}

现在的样子:

如果需要在选项卡控件中添加按钮,应该将hTab作为按钮的父class传递:

CreateButtons(hTab);

并且根据documentation

x

Type: int

The initial horizontal position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the window's upper-left corner, in screen coordinates. For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent window's client area. If this parameter is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x and y parameters are set to zero.

所以设置的坐标是相对于parent的左上角window。不需要通过GetWindowRect获取hTab的位置信息,而是像下面的代码一样传递相对标签控件的位置:

int cx_initPos = 10;
int cx = cx_initPos;

对我有用:

补充:如果需要针对不同的tab页进行调整,可以参考这个

编辑:

您可以尝试在 RECT rt = GetLocalCoordinates(hTab);:

之后添加 TabCtrl_AdjustRect(hTab, FALSE, &rt);
RECT rt;
rt = GetLocalCoordinates(hTab);
TabCtrl_AdjustRect(hTab, FALSE, &rt);

这是你想要的结果吗: