如何制作带有子元素的选项卡控件?

How do I make a tab control with children elements?

我正在学习 WINAPI,我读到没有像 C# 的 TabControl 控件那样的子控件,因此您必须自己创建元素和 show/hide。我读到它可以通过在选项卡页面区域内绘制一个对话框来完成,所以我去创建一个无边框对话框作为选项卡控件的子控件,以使其具有像 C# 的效果。但是我还是做不到。我的对话框是浮动的,而不是选项卡控件的子控件。我不知道将它放在标签页内,我尝试将 hWndParent 设置为标签控件的 HWND 和 dwExStyle 中的 WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT 标志,但仍然漂浮在标签控件上。欢迎使用不同的方法来解决这个问题。我正在创建这样的选项卡控件:

void AddTabControl(HWND hwnd)
{
    hTab = CreateWindowW(WC_TABCONTROLW, NULL,
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP,
            0, 30, 250, 250,
            hwnd,
            (HMENU) 9,
            NULL,
            NULL);
    InsertTabItem(hTab, 10, L"A");
    InsertTabItem(hTab, 11, L"B");
}

void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text)
{
    TCITEMW tci = {0};
    tci.mask = TCIF_TEXT;
    tci.pszText = text;
    tci.cchTextMax = lstrlenW(text);
    if(SendMessage(tabHwnd, TCM_INSERTITEMW, id, (LPARAM) &tci) == -1) {
        MessageBox(NULL, 
                    L"couldn't create the new tab page", 
                    L"tab errror",
                    MB_OK | MB_ICONERROR);
    }
}

对话框是这样的:

void CreateDialogBox(HWND hwnd)
{
  CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT, // WS_EX_TOOLWINDOW to hide window from ALT+TAB
        L"DialogClass", L"Dialog Box",
        WS_VISIBLE | WS_POPUP | WS_SYSMENU,
        100, 100, 400, 150,
        hTab, NULL, ghInstance,  NULL
  );
}

结果是:

预期结果(C#制作,举个例子,忽略色差,稍后修复):

完整代码如下:

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

#define UNICODE

#include <windows.h>
#include <Commctrl.h>
#include <strsafe.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

void CreateDialogBox(HWND);
void RegisterDialogClass(HWND);
void AddTabControl(HWND hwnd);
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text);

HINSTANCE ghInstance;
HWND mainWindow;
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;
  
  RegisterClassW(&wc);
  hwnd = CreateWindowW(wc.lpszClassName, L"Window",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 500, 350, NULL, NULL, hInstance, NULL);  
  mainWindow = hwnd;
  ghInstance = hInstance;

  while( GetMessage(&msg, NULL, 0, 0)) {
    DispatchMessage(&msg);
  }
  
  return (int) msg.wParam;
}

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

  switch(msg)
  {
      case WM_CREATE:
          RegisterDialogClass(hwnd);
          AddTabControl(hwnd);
          CreateDialogBox(hwnd);
          break;

      case WM_COMMAND:
          CreateDialogBox(hwnd);
          break;

      case WM_DESTROY:
      {
          PostQuitMessage(0);
          return 0;
      }
  }
  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg) {
  
    case WM_CREATE:
        CreateWindowW(L"button", L"A",    
          WS_VISIBLE | WS_CHILD ,
          50, 50, 80, 25, hwnd, (HMENU) 1, NULL, NULL);  
        CreateWindowW(L"button", L"B", 
          WS_VISIBLE | WS_CHILD ,
          150, 50, 80, 25, hwnd, (HMENU) 2, NULL, NULL);
        CreateWindowW(L"button", L"C",
          WS_VISIBLE | WS_CHILD ,
          250, 50, 80, 25, hwnd, (HMENU) 3, NULL, NULL); 
    break;

    case WM_COMMAND:
        DestroyWindow(hwnd);
    break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;

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

void RegisterDialogClass(HWND hwnd) 
{
  WNDCLASSEXW wc = {0};
  wc.cbSize           = sizeof(WNDCLASSEXW);
  wc.lpfnWndProc      = (WNDPROC) DialogProc;
  wc.hInstance        = ghInstance;
  wc.hbrBackground    = GetSysColorBrush(COLOR_3DFACE);
  wc.lpszClassName    = L"DialogClass";
  RegisterClassExW(&wc);
}

void CreateDialogBox(HWND hwnd)
{
  CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT, // WS_EX_TOOLWINDOW to hide window from ALT+TAB
        L"DialogClass", L"Dialog Box",
        WS_VISIBLE | WS_POPUP | WS_SYSMENU,
        100, 100, 400, 150,
        hTab, NULL, ghInstance,  NULL
  );
}

void AddTabControl(HWND hwnd)
{
    hTab = CreateWindowW(WC_TABCONTROLW, NULL,
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP,
            0, 30, 250, 250,
            hwnd,
            (HMENU) 9,
            NULL,
            NULL);
    InsertTabItem(hTab, 10, L"A");
    InsertTabItem(hTab, 11, L"B");
}

void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text)
{
    TCITEMW tci = {0};
    tci.mask = TCIF_TEXT;
    tci.pszText = text;
    tci.cchTextMax = lstrlenW(text);
    if(SendMessage(tabHwnd, TCM_INSERTITEMW, id, (LPARAM) &tci) == -1) {
        MessageBox(NULL, 
                    L"couldn't create the new tab page", 
                    L"tab errror",
                    MB_OK | MB_ICONERROR);
    }
}

您在创建对话框控件时,缺少WS_CHILD样式,添加了WS_POPUP样式,导致生成的对话框浮动。

只需要修改成WS_CHILD样式,适当修改控件的大小即可达到你想要的效果。

void CreateDialogBox(HWND hwnd)
{
    CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT, // WS_EX_TOOLWINDOW to hide window from ALT+TAB
        L"DialogClass", L"Dialog Box",
        WS_VISIBLE  | WS_SYSMENU | WS_CHILD ,
        10, 30, 350, 150,
        hTab, NULL, ghInstance, NULL
    );
}

void AddTabControl(HWND hwnd)
{
    hTab = CreateWindowW(WC_TABCONTROLW, NULL,
        WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP,
        10, 30, 400, 250,
        hwnd,
        (HMENU)9,
        NULL,
        NULL);
    InsertTabItem(hTab, 10, L"A");
    InsertTabItem(hTab, 11, L"B");
}

它是这样工作的: