0

I'm learning WINAPI, I read there's no children control in the sense a C#'s TabControl control does, so you have to create elements and show/hide by yourself. I read it may be done by drawing a dialog box inside tab page's area so I went to create a borderless dialog box being tab control's child, to make it have effect like C#'s. But I still couldn't make it. My dialog box is floating rather being tab control's child. I don't know to make it inside the tab page, I've tried setting the hWndParent to tab control's HWND and WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT flags in dwExStyle but still is floating over the tab control. Different approaches to solve this are welcome. I'm creating the tab control like this:

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);
    }
}

And the dialog box like this:

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
  );
}

the result is:

enter image description here

the expected result (made from C#, just for example, ignore the color differences, I'll fix this later):

enter image description here

here's the full code:

#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);
    }
}
Jack
  • 16,276
  • 55
  • 159
  • 284
  • Use the CreateDialog (or CreateDialogParam) API to create a modeless dialog, I generally specify the tab control itself as the parent (because it is then easy to use TCM_ADJUSTRECT to calculate the area where the child goes) without needing to remap to some other window's client coordinates. – SoronelHaetir Dec 22 '20 at 20:32
  • @SoronelHaetir thanks for the tip but can I use `CreateDialog` or `CreateDialogParam` without deal with resources? – Jack Dec 22 '20 at 20:35
  • Well, you could use CreateDialogIndirect and not have a resource but you will still need a dialog template (which is an enormous pain without resource scripts). Honestly, if you aren't taking advantage of the dialog class automatic child window creation I'm not sure why you would use the dialog class at all. A tab 'child' of this form can be any window class, not just a dialog. – SoronelHaetir Dec 22 '20 at 20:47
  • I agree with SoronelHaetir, the only reason to use dialog boxes as tab panes is if you want to use the dialog box resource editor; there is no other benefit. ANway take a look at the code I posted here: https://stackoverflow.com/a/56430730/1413244 – jwezorek Dec 22 '20 at 20:55
  • @SoronelHaetir I thought I could just create the tab page with `CreateWindowExW()` then add the controls I need in the `DialogProc()` procedure and attach this tab page to the tab control somehow, making it its child or drawing it in the tab control area and hide/display the tab pages(dialog boxes) according to the selected tab's index. Isn't that approach possible? – Jack Dec 22 '20 at 21:12
  • @jwezorek My original reasoning is I could create those controls within a dialog box then put that dialog box(which is the tab page) as child of tab control's and show/hide according to the selected tab. I stuck at part of making the dialog control be the tab control's child, inside that window, rather a floating window on top of the main window. Isn't my approach possible? thanks for your code sample, I managed to do that then switch to the approach I'm trying to use, in order to automatize the controls creation. – Jack Dec 22 '20 at 21:34
  • this shows how to make modeless dialog that is a child of another window, I believe: https://learn.microsoft.com/en-us/windows/win32/controls/create-a-tabbed-dialog-box – jwezorek Dec 22 '20 at 21:43

1 Answers1

2

When you created the dialog box control, you lacked the WS_CHILD style and added the WS_POPUP style, which caused the dialog box you generated to float.

You only need to modify it to the WS_CHILD style, and appropriately modify the size of the control to achieve your desired effect.

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");
}

It works like this:

enter image description here

Zeus
  • 3,703
  • 3
  • 7
  • 20