Add Controls To Specific Tab Page in TabControl in C++ Win32

1

I want to add some controls to the tab page in tabcontrol but it seems that it will be added to all pages and there is not tab page in tabcontrol by default.

I have read these links below but they did not help me and in some parts of them, confused me.

How to add controls to a Tab control

http://www.cplusplus.com/forum/windows/37161/

https://msdn.microsoft.com/en-us/library/bb760551.aspx

https://msdn.microsoft.com/en-us/library/hh298366.aspx

https://msdn.microsoft.com/en-us/library/ms645398.aspx

Here is my code :

[Code]:

#define ID_LBL 500              
#define ID_BTN 501              
#define ID_TBC 502              

HWND hWnd;

void InserTabItem(HWND handle, LPWSTR text, int id)
{
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = wcslen(text);
SendMessage(handle, TCM_INSERTITEM, id, LPARAM(&tci));
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CREATE:
{
    HWND button_handle = 0;
    HWND label_handle = 0;
    HWND tab_handle = 0;
    tab_handle = CreateWindowEx(WS_EX_CONTROLPARENT, WC_TABCONTROL, 0, WS_VISIBLE | WS_CHILD, 10, 10, 200, 150, hWnd, HMENU(ID_TBC), 0, 0);
    InserTabItem(tab_handle, L"page1", 0);
    InserTabItem(tab_handle, L"page2", 1);
    button_handle = CreateWindowEx(0, WC_BUTTON, L"test-button-page2", WS_VISIBLE | WS_CHILD, 10, 50, 150, 30, tab_handle, HMENU(ID_BTN), 0, 0);
    label_handle = CreateWindowEx(0, WC_STATIC, L"test-label-page1", WS_VISIBLE | WS_CHILD, 10, 100, 150, 30, tab_handle, HMENU(ID_LBL), 0, 0);
}
break;
case WM_CLOSE:
    DestroyWindow(hWnd);
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
default:
    return DefWindowProc(hWnd, Msg, wParam, lParam);
    break;
}

return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreviewInstance, LPSTR lpcmdline, int ncmdshow)
{
WNDCLASSEX wndexcls;
wndexcls.lpszClassName = L"win";
wndexcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndexcls.hbrBackground = (HBRUSH)(COLOR_3DSHADOW + 1);
wndexcls.lpszMenuName = NULL;
wndexcls.style = NULL;
wndexcls.hInstance = hInstance;
wndexcls.cbSize = sizeof(WNDCLASSEX);
wndexcls.cbClsExtra = 0;
wndexcls.cbWndExtra = 0;
wndexcls.lpfnWndProc = WndProc;
RegisterClassEx(&wndexcls);

hWnd = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_CONTROLPARENT, L"win", L"TestApp", WS_OVERLAPPEDWINDOW, 100, 100, 640, 380, 0, 0, hInstance, 0);
ShowWindow(hWnd, ncmdshow);
UpdateWindow(hWnd);



MSG wnd_msg;
while (GetMessage(&wnd_msg, NULL, 0, 0)>0)
{
    TranslateMessage(&wnd_msg);
    DispatchMessage(&wnd_msg);
}
return (int)wnd_msg.wParam;

}

I am looking for a safe and proper implementation.

Thanks for any help

========================================================

[Update]:

Thanks for comments but no answer in detail :(

Although that is not the implementation I am looking for(NotDialogBased), But from the forth link I mentioned :

How to Create a Tabbed Dialog Box :

https://msdn.microsoft.com/en-us/library/hh298366.aspx

Here is my code of that page :

[resource.h]:

#define IDD_Page1                       101
#define IDD_Page2                       102
#define IDD_Page3                       103
#define IDD_Main_Dialog                 104
#define IDC_BTN_Page1                   1001
#define IDC_BTN2_Page1                  1002
#define IDC_BTN_Page2                   1013
#define IDC_BTN_Page3                   1014

[Resource.rc]:

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

 //////////////////////////////////////////////////
 //
 // Dialog
  //

 IDD_Page1 DIALOGEX 0, 0, 313, 178
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 PUSHBUTTON      "Button2-Page1",IDC_BTN2_Page1,129,107,67,22
 PUSHBUTTON      "Button-Page1",IDC_BTN_Page1,127,77,67,22
 END

 IDD_Page2 DIALOGEX 0, 0, 309, 177
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 PUSHBUTTON      "Button-Page2",IDC_BTN_Page2,120,77,60,18
 END

 IDD_Page3 DIALOGEX 0, 0, 309, 177
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 PUSHBUTTON      "Button-Page3",IDC_BTN_Page3,120,73,64,25
 END

 IDD_Main_Dialog DIALOGEX 0, 0, 309, 177
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 END


////////////////////////////////////////////
//
// DESIGNINFO
 //

 #ifdef APSTUDIO_INVOKED
 GUIDELINES DESIGNINFO
 BEGIN
 IDD_Page1, DIALOG
 BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 306
    TOPMARGIN, 7
    BOTTOMMARGIN, 171
END

IDD_Page2, DIALOG
BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 302
    TOPMARGIN, 7
    BOTTOMMARGIN, 170
END

IDD_Page3, DIALOG
BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 302
    TOPMARGIN, 7
    BOTTOMMARGIN, 170
END

IDD_Main_Dialog, DIALOG
BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 302
    TOPMARGIN, 7
    BOTTOMMARGIN, 170
END
END
#endif    // APSTUDIO_INVOKED

#endif    

[Main.cpp]:

#include <windows.h>
#include <CommCtrl.h>
#include "resource.h"
#pragma comment(lib, "ComCtl32.lib")
#define C_PAGES 3 

INT_PTR CALLBACK DialogProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

HWND Win_Handle;
HWND Dailog_Handle;
HINSTANCE hInstance_Win_Global;

typedef struct {
WORD      dlgVer;
WORD      signature;
DWORD     helpID;
DWORD     exStyle;
DWORD     style;
WORD      cDlgItems;
short     x;
short     y;
short     cx;
short     cy;
WORD      pointsize;
WORD      weight;
BYTE      italic;
BYTE      charset;
} DLGTEMPLATEEX;

typedef struct tag_dlghdr {
HWND hwndTab;       // tab control 
HWND hwndDisplay;   // current child dialog box 
RECT rcDisplay;     // display rectangle for the tab control 
DLGTEMPLATEEX *apRes[C_PAGES];
} DLGHDR;

void InserTabItem(HWND handle, LPWSTR text, int id)
{
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = wcslen(text);
SendMessage(handle, TCM_INSERTITEM, id, LPARAM(&tci));
}

DLGTEMPLATEEX* DoLockDlgRes(LPCTSTR lpszResName)
{
HRSRC hrsrc = FindResource(NULL, lpszResName, RT_DIALOG);
HGLOBAL hglb = LoadResource(hInstance_Win_Global, hrsrc);
return (DLGTEMPLATEEX *)LockResource(hglb);
}

VOID WINAPI OnChildDialogInit(HWND hwndDlg)
{
HWND hwndParent = GetParent(hwndDlg);
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(
    hwndParent, GWL_USERDATA);
SetWindowPos(hwndDlg, NULL, pHdr->rcDisplay.left,
    pHdr->rcDisplay.top,//-2,
    (pHdr->rcDisplay.right - pHdr->rcDisplay.left),
    (pHdr->rcDisplay.bottom - pHdr->rcDisplay.top),
    SWP_SHOWWINDOW);

return;
} 
VOID OnSelChanged(HWND hwndDlg)
{
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(hwndDlg, GWL_USERDATA);
int iSel = TabCtrl_GetCurSel(pHdr->hwndTab);
if (pHdr->hwndDisplay != NULL)
    DestroyWindow(pHdr->hwndDisplay);
pHdr->hwndDisplay = CreateDialogIndirect(hInstance_Win_Global,
    (DLGTEMPLATE *)pHdr->apRes[iSel], hwndDlg,DialogProc);
}
HRESULT OnTabbedDialogInit(HWND hwndDlg)
{
INITCOMMONCONTROLSEX iccex;
DWORD dwDlgBase = GetDialogBaseUnits();
int cxMargin = LOWORD(dwDlgBase) / 4;
int cyMargin = HIWORD(dwDlgBase) / 8;

TCITEM tie;
RECT rcTab;
HWND hwndButton;
RECT rcButton;
int i;

iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&iccex);

DLGHDR *pHdr = (DLGHDR *)LocalAlloc(LPTR, sizeof(DLGHDR));
SetWindowLong(hwndDlg, GWL_USERDATA, (LONG)pHdr);

pHdr->hwndTab = CreateWindow(
    WC_TABCONTROL, L"",
    WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
    0, 0, 300, 200,
    hwndDlg, NULL, hInstance_Win_Global, NULL
    );
if (pHdr->hwndTab == NULL)
{
    return HRESULT_FROM_WIN32(GetLastError());
}

tie.mask = TCIF_TEXT | TCIF_IMAGE;
tie.iImage = -1;
tie.pszText = L"First";
TabCtrl_InsertItem(pHdr->hwndTab, 0, &tie);
tie.pszText = L"Second";
TabCtrl_InsertItem(pHdr->hwndTab, 1, &tie);
tie.pszText = L"Third";
TabCtrl_InsertItem(pHdr->hwndTab, 2, &tie);

pHdr->apRes[0] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page1));
pHdr->apRes[1] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page2));
pHdr->apRes[2] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page3));

SetRectEmpty(&rcTab);
for (i = 0; i < C_PAGES; i++)
{
    if (pHdr->apRes[i]->cx > rcTab.right)
        rcTab.right = pHdr->apRes[i]->cx;
    if (pHdr->apRes[i]->cy > rcTab.bottom)
        rcTab.bottom = pHdr->apRes[i]->cy;
}

MapDialogRect(hwndDlg, &rcTab);

TabCtrl_AdjustRect(pHdr->hwndTab, TRUE, &rcTab);
OffsetRect(&rcTab, cxMargin - rcTab.left, cyMargin - rcTab.top);

CopyRect(&pHdr->rcDisplay, &rcTab);
TabCtrl_AdjustRect(pHdr->hwndTab, FALSE, &pHdr->rcDisplay);

SetWindowPos(pHdr->hwndTab, NULL, rcTab.left, rcTab.top,
    rcTab.right - rcTab.left, rcTab.bottom - rcTab.top,
    SWP_NOZORDER);

hwndButton = GetDlgItem(hwndDlg, IDC_BTN_Page1);
SetWindowPos(hwndButton, NULL,
    rcTab.left, rcTab.bottom + cyMargin, 0, 0,
    SWP_NOSIZE | SWP_NOZORDER);

GetWindowRect(hwndButton, &rcButton);
rcButton.right -= rcButton.left;
rcButton.bottom -= rcButton.top;

hwndButton = GetDlgItem(hwndDlg, IDC_BTN2_Page1);
SetWindowPos(hwndButton, NULL,
    rcTab.left + rcButton.right + cxMargin,
    rcTab.bottom + cyMargin, 0, 0,
    SWP_NOSIZE | SWP_NOZORDER);

SetWindowPos(hwndDlg, NULL, 0, 0,
    rcTab.right + cyMargin + (2 * GetSystemMetrics(SM_CXDLGFRAME)),
    rcTab.bottom + rcButton.bottom + (2 * cyMargin)
    + (2 * GetSystemMetrics(SM_CYDLGFRAME))
    + GetSystemMetrics(SM_CYCAPTION),
    SWP_NOMOVE | SWP_NOZORDER);

OnSelChanged(hwndDlg);

return S_OK;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CREATE:
{
    Dailog_Handle = CreateDialogParam(hInstance_Win_Global, MAKEINTRESOURCE(IDD_Main_Dialog), hWnd, DialogProc, 0);
    ShowWindow(Dailog_Handle, SW_SHOWDEFAULT);
    UpdateWindow(Dailog_Handle);
    SetWindowPos(Dailog_Handle, 0, 10, 10, 500, 300, SWP_NOZORDER);

}
break;
case WM_CLOSE:
    DestroyWindow(hWnd);
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
default:
    return DefWindowProc(hWnd, Msg, wParam, lParam);
    break;
}

return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_INITDIALOG:
{

    OnTabbedDialogInit(hWnd);
    OnChildDialogInit(hWnd);

    return (INT_PTR)TRUE;
}
    break;
case WM_NOTIFY:
{
    switch (((LPNMHDR)lParam)->code)
    {
    case TCN_SELCHANGE:
    {
        OnSelChanged(hWnd);
    }
    break;
    default:
        break;
    }

}
break;
case WM_CLOSE:
    DestroyWindow(hWnd);
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
}
return (INT_PTR)FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreviewInstance, LPSTR lpcmdline, int ncmdshow)
  {
  WNDCLASSEX wndexcls;
  wndexcls.lpszClassName = L"win";
  wndexcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wndexcls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
  wndexcls.hCursor = LoadCursor(NULL, IDC_ARROW);
  wndexcls.hbrBackground = (HBRUSH)(COLOR_3DSHADOW + 1);
  wndexcls.lpszMenuName = NULL;
  wndexcls.style = NULL;
  wndexcls.hInstance = hInstance;
  wndexcls.cbSize = sizeof(WNDCLASSEX);
  wndexcls.cbClsExtra = 0;
  wndexcls.cbWndExtra = 0;
  wndexcls.lpfnWndProc = WndProc;
  RegisterClassEx(&wndexcls);

 Win_Handle = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_CONTROLPARENT, L"win", L"TestApp", WS_OVERLAPPEDWINDOW, 100, 100, 640, 380, 0, 0, hInstance, 0);
 hInstance_Win_Global = hInstance;
 ShowWindow(Win_Handle, SW_SHOWDEFAULT);
 UpdateWindow(Win_Handle);


MSG wnd_msg;
while (GetMessage(&wnd_msg, NULL, 0, 0)>0)
{
    TranslateMessage(&wnd_msg);
    DispatchMessage(&wnd_msg);
}
return (int)wnd_msg.wParam;
}

The Problem is that , after debugging , the application exites and does not show anything. if I comment the OnSelChanged(hWnd) and OnChildDialogInit(hWnd) the application starts normally and shows the tabcontrol but not the controls on the pages. it seems that the problem is here.

the output log :

  First-chance exception at 0x00BE1886 in testcppapp.exe: 0xC0000005: Access violation reading location 0x00000014.
 The program '[16220] testcppapp.exe' has exited with code 0 (0x0).

I have Read the link below about Access violation reading location:

http://www.cplusplus.com/forum/general/17094/

But I can not fix the problem.

Please Post your Answer and Explain about it , not just brief in comments !

Thanks for any help.

winapi
tabcontrol
asked on Stack Overflow Dec 8, 2015 by BlueFlower • edited Dec 9, 2015 by BlueFlower

2 Answers

5

One problem is here:

pHdr->hwndDisplay = CreateDialogIndirect(hInstance_Win_Global, 
    (DLGTEMPLATE*)pHdr->apRes[iSel], hwndDlg, DialogProc);

You are reusing the same dialog procedure for both main dialog and child dialogs. Main dialog creates child dialogs, child dialogs use the same procedure to create child dialogs... Also there are no error checks.

Beyond that, this code is too complicated. Just use a dialog box for main window. Create a new dialog IDD_DIALOG1 and drag/drop a tab control in it. Assign IDC_TAB1 for tab control ID. Try starting with this code instead:

#include <Windows.h>
#include <CommCtrl.h>
#include "Resource.h"

#pragma comment(lib,"comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

HINSTANCE g_hinst;

struct TData {
    HWND page1, page2, page3;
    HWND tab;
} data;

BOOL CALLBACK DialogPage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch(msg) {
    case WM_COMMAND:
        switch (wp) {
            //...
        }
    }
    return FALSE;
}

void OnSelChange() {
    int sel = TabCtrl_GetCurSel(data.tab);
    ShowWindow(data.page1, (sel == 0) ? SW_SHOW : SW_HIDE);
    ShowWindow(data.page2, (sel == 1) ? SW_SHOW : SW_HIDE);
}

BOOL CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg) {
    case WM_INITDIALOG: {
        data.page1 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_Page1), hwnd, DialogPage);
        data.page2 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_Page2), hwnd, DialogPage);

        data.tab = GetDlgItem(hwnd, IDC_TAB1);
        if (data.tab)
        {
            TCITEM tci = { 0 };
            tci.mask = TCIF_TEXT;
            tci.pszText = L"Page1";
            TabCtrl_InsertItem(data.tab, 0, &tci);
            tci.pszText = L"Page2";
            TabCtrl_InsertItem(data.tab, 1, &tci);

            RECT rc;//find tab control's rectangle
            GetWindowRect(data.tab, &rc);
            POINT offset = { 0 };
            ScreenToClient(hwnd, &offset);
            OffsetRect(&rc, offset.x, offset.y); //convert to client coordinates
            rc.top += 50;
            SetWindowPos(data.page1, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);
            SetWindowPos(data.page2, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);

            OnSelChange();
        }

        break;
    }

    case WM_NOTIFY: {
        switch (((LPNMHDR)lp)->code) {
        case TCN_SELCHANGE:
            OnSelChange();
            break;
        }
    }
    break;

    case WM_COMMAND:
        switch (wp) {
        case IDOK: EndDialog(hwnd, wp);  break;
        case IDCANCEL:  EndDialog(hwnd, wp);  break;
        }
    }

    return FALSE;
}

int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE, LPWSTR, int)
{
    g_hinst = hinst;
    DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc);
    return 0;
}
answered on Stack Overflow Dec 11, 2015 by Barmak Shemirani • edited Dec 11, 2015 by Barmak Shemirani
0

An approach that I am testing and looks promising is to use a dialog box template for each pane of the tab control within the modal dialog box.

For testing, I generated a starting Win32 GUI application and then hijacked the About box display triggered by IDM_ABOUT to display my own dialog box with a tab control rather than a standard About dialog.

        case IDM_ABOUT:
//          DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            {
                CDialogTabTest x;
                x.CreateModal (hWnd, hInst);
            }
            break;

After some research and refactoring and development I have the following approach.

I have created three classes for implementation: CDialogTest (handles the dialog box functionality), CDialogTabTest (handles the tab control within the dialog box), and CDialogTabPane (handles the individual tab panes that are displayed in the tab control).

The CDialogTest class provides the functionality for basic dialog box behavior. It allows for two kinds of dialog boxes, modal used to create the actual modal dialog box and non-modal used to create the tab control panes.

The CDialogTest class is designed to be used as a subclass and be extended for a particular dialog box or tab control pane.

The CDialogTabTest class extends CDialogTest to implement a modal dialog box. The CDialogTabPane class extends CDialogTest to implement a non-modal dialog box that is used as a tab control tab pane.

For MFC programmers some of the names may sound vaguely familiar as while working this out, I now understand some of the mechanics of the MFC classes for dialog boxes.

I put added some additional include files in the the stdafx.h file as that was a central place to put them for what I needed.

// TODO: reference additional headers your program requires here

#include "commctrl.h"
#include "windowsx.h"

#include <vector>

DialogTest.h

#pragma once

class CDialogTest
{
private:
    static INT_PTR CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

public:
    HINSTANCE m_hInst;
    HWND      m_hParent;
    HWND      m_hDlg;

    CDialogTest();
    CDialogTest(HINSTANCE hInst, HWND hParent = NULL);
    virtual ~CDialogTest(void);

    HWND CreateThing (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst);
    INT_PTR WINAPI CreateModal (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst);

    virtual int DoDataExchange (int iDir) { return 0; }
    virtual int OnWmCommandInit () { return 0; }
    virtual int OnWmNotify (LPNMHDR    pNmhdr) { return 0; }
    virtual int OnWmCommand (WPARAM wParam, LPARAM lParam) { return 0; }

};

and DialogTest.cpp

#include "StdAfx.h"
#include "DialogTest.h"

CDialogTest::CDialogTest(void)
{
}

CDialogTest::CDialogTest(HINSTANCE hInst, HWND hParent /* = NULL */) : 
    m_hInst (hInst), m_hParent(hParent)
{
}

CDialogTest::~CDialogTest(void)
{
}

INT_PTR CALLBACK CDialogTest::DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_INITDIALOG:
        {
            // Set the provided object pointer into the dialog box user area
            // so that we can use it to provide the necessary notifications
            // to what ever object which has derived from this class.
            ::SetWindowLongPtr (hDlg, DWLP_USER, lParam);

            // ensure that the object knows its dialog handle.
            CDialogTest  *pObj = (CDialogTest *)lParam;
            pObj->m_hDlg = hDlg;

            // call the objects handlers to initialize the dialog further
            // and to then populate the data displayed in the dialog.
            pObj->OnWmCommandInit ();
            pObj->DoDataExchange (1);
        }
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        // if this command is an Ok or Cancel button press then we are
        // done. other command messages are routed to the derived object's
        // how command message handler for further processing.
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            if (LOWORD(wParam) == IDOK) {
                LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
                CDialogTest  *pObj = (CDialogTest *)lP;

                pObj->DoDataExchange (0);
            }

            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        } else {
            LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
            CDialogTest  *pObj = (CDialogTest *)lP;

            pObj->OnWmCommand (wParam, lParam);
        }
        break;

    case WM_NOTIFY:
        {
            LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
            CDialogTest  *pObj = (CDialogTest *)lP;
            LPNMHDR    pNmhdr = ((LPNMHDR)lParam);

            pObj->OnWmNotify (pNmhdr);
        }
        break;
    }
    return (INT_PTR)FALSE;
}

// We need two different kinds of dialog create functions as we support
// both a modal dialog and a modeless dialog. The modal dialog is used to
// display an actual dialog box. The modeless dialog is used to display
// a tab control pane using a dialog template.
INT_PTR WINAPI CDialogTest::CreateModal (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst)
{
    m_hInst = hInst;
    m_hParent = hWnd;

    // display the modal dialog box and return the identifier for the button clicked.
    return DialogBoxParam(hInst, lpTemplateName, hWnd, &CDialogTest::DlgProc, (LPARAM)this);
}

HWND CDialogTest::CreateThing (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst)
{
    m_hInst = hInst;
    m_hParent = hWnd;

    // create a modeless dialog box to be used as a tab control pane or similar
    // non-modal dialog box use.
    m_hDlg =  ::CreateDialogParam(hInst, lpTemplateName, hWnd, &CDialogTest::DlgProc, (LPARAM)this);

    return m_hDlg;
}

The class CDialogTabTest creates the modal dialog box that is being displayed as a result of the IDM_ABOUT menu selection message in the main application message handler.

DialogTabTest.h

#pragma once

#include "stdafx.h"

#include "DialogTest.h"
#include "DialogTabPane.h"

#include "resource.h"

struct CCheckBoxData
{
    UINT          iCntrlId;
    int           iCheck;
    std::wstring  prompt;
};

typedef std::vector<CCheckBoxData> CCheckBoxVector;


class CDialogTabTest : public CDialogTest
{
protected:
    CDialogTabPane           m_hTabPane1;
    CDialogTabPane           m_hTabPane2;
    CDialogTabPane           m_hTabPane3;
    enum {IDD_DIALOG = IDD_DIALOG_TAB00};

public:
    CDialogTabTest(void);
    virtual ~CDialogTabTest(void);

    INT_PTR WINAPI CreateModal (HWND hWnd, HINSTANCE hInst);

    static int SetValues (CCheckBoxData &x, HWND hDlg);
    static int GetValues (CCheckBoxData &x, HWND hDlg);

    virtual int DoDataExchange (int iDir);
    virtual int OnWmCommandInit ();
    virtual int OnWmNotify (LPNMHDR    pNmhdr);
};

and DialogTabTest.cpp which creates the modal dialog box and initializes it.

#include "StdAfx.h"

#include "DialogTabTest.h"


CDialogTabTest::CDialogTabTest(void)
{
}

CDialogTabTest::~CDialogTabTest(void)
{
}

int CDialogTabTest::SetValues (CCheckBoxData &x, HWND hDlg)
{
    HWND  hWnd = GetDlgItem (hDlg, x.iCntrlId);
    ::SetWindowText (hWnd, x.prompt.c_str());

    Button_SetCheck (hWnd, x.iCheck);
    return 0;
}

int CDialogTabTest::GetValues (CCheckBoxData &x, HWND hDlg)
{
    HWND  hWnd = GetDlgItem (hDlg, x.iCntrlId);
    x.iCheck = Button_GetCheck (hWnd);
    return 0;
}

int CDialogTabTest::OnWmCommandInit ()
{
    // get the window handle for the tab control. This is going to
    // be the parent window for all of the tab panes that are inserted
    // into the tab control. By making the tab control the parent,
    // the tab panes will be contained within the tab control and
    // will flow with it should it be moved about.
    HWND hTabCntrl = GetDlgItem (m_hDlg, IDC_TAB1);

    TCITEM tie = {0};
    tie.mask = TCIF_TEXT | TCIF_IMAGE; 
    tie.iImage = -1;

    tie.pszText = L"First";
    m_hTabPane1.CreateThing (&tie, 0, MAKEINTRESOURCE(IDD_DIALOG_TAB01), m_hInst, hTabCntrl);

    tie.pszText = L"Second";
    m_hTabPane2.CreateThing (&tie, 1, MAKEINTRESOURCE(IDD_DIALOG_TAB02), m_hInst, hTabCntrl);

    tie.pszText = L"Third";
    m_hTabPane3.CreateThing (&tie, 2, MAKEINTRESOURCE(IDD_DIALOG_TAB03), m_hInst, hTabCntrl);

    // set the first tab to be displayed.
    NMHDR   Nmhdr = {hTabCntrl, 0, TCN_SELCHANGE};
    m_hTabPane1.OnWmNotify (&Nmhdr);

    return 0;
}

int CDialogTabTest::OnWmNotify (LPNMHDR    pNmhdr)
{
    // propagate the notification to the various panes in our tab control
    // so that they can do what they need to do.
    m_hTabPane1.OnWmNotify (pNmhdr);
    m_hTabPane2.OnWmNotify (pNmhdr);
    m_hTabPane3.OnWmNotify (pNmhdr);

    return 0;
}

int CDialogTabTest::DoDataExchange (int iDir)
{
    if (iDir) {
        CCheckBoxData  iVal = {IDC_CHECK1, BST_UNCHECKED, L"DoData: Check Box 1 Set and Not Checked"};

        SetValues (iVal, m_hTabPane1.m_hDlg);

        iVal.iCntrlId = IDC_CHECK2;
        iVal.iCheck = BST_CHECKED;
        iVal.prompt = L"DoData: Check Box 2 is Set and Checked.";
        SetValues (iVal, m_hTabPane1.m_hDlg);

        iVal.iCntrlId = IDC_CHECK4;
        iVal.iCheck = BST_CHECKED;
        iVal.prompt = L"DoData: Check Box 4 is Set, Checked Tab 3.";
        SetValues (iVal, m_hTabPane3.m_hDlg);
    } else {
        CCheckBoxData  iVal = {IDC_CHECK1, BST_UNCHECKED, L""};
        GetValues (iVal, m_hTabPane1.m_hDlg);
    }

    return 0;
}

INT_PTR WINAPI CDialogTabTest::CreateModal (HWND hWnd, HINSTANCE hInst)
{
    // this is a modal dialog box so we use the CreateModal() method
    // in order to create the dialog box and display it.
    return CDialogTest::CreateModal(hWnd, MAKEINTRESOURCE(IDD_DIALOG), hInst);
}

Finally there is the CDialogTabPane class which implements the functionality for individual tab control tab panes.

DialogTabPane.h

#pragma once

#include "stdafx.h"
#include "DialogTest.h"

class CDialogTabPane : public CDialogTest
{
protected:
    int  m_iTab;

public:
    DWORD  m_dwLastError;

    CDialogTabPane(void);
    virtual ~CDialogTabPane(void);

    HWND CreateThing (LPTCITEM pTie, int iTab, LPCWSTR lpTemplateName, HINSTANCE hInstance, HWND hWndParent);

//  virtual int DoDataExchange (int iDir);
//  virtual int OnWmCommandInit ();
    virtual int OnWmNotify (LPNMHDR    pNmhdr);
};

and DialogTabPane.cpp

#include "StdAfx.h"

#include "DialogTabPane.h"


CDialogTabPane::CDialogTabPane(void)
{
}


CDialogTabPane::~CDialogTabPane(void)
{
}


HWND CDialogTabPane::CreateThing (LPTCITEM pTie, int iTab, LPCWSTR lpTemplateName, HINSTANCE hInstance, HWND hWndParent)
{
    // insert the new tab into the tab control with the specified parameters.
    TabCtrl_InsertItem(hWndParent, iTab, pTie);
    m_iTab = iTab;

    // We need to figure out the adjustment of the dialog window position
    // within the tab control display area so that when the dialog and its
    // controls are displayed for a given tab the tab selection list can
    // still be seen above the top of the displayed dialog.
    // The tab selection list must be visible so that the user can select
    // any of the specified tabs.
    RECT  tabDisplay = {0};
    GetWindowRect (hWndParent, &tabDisplay);
    RECT  dialogDisplay = tabDisplay;
    // NOTE: TabCtrl_AdjustRect() - This message applies only to tab controls that
    //       are at the top. It does not apply to tab controls that are on the
    //       sides or bottom.
    //       But then tab controls on the side or bottom are an abomination.
    TabCtrl_AdjustRect (hWndParent, FALSE, &dialogDisplay);
    dialogDisplay.left -= tabDisplay.left; dialogDisplay.top -= tabDisplay.top;

    m_dwLastError = 0;

    // create our dialog and then position it within the tab control properly.
    CDialogTest::CreateThing(hWndParent, lpTemplateName, hInstance);
    if (!::SetWindowPos (m_hDlg, HWND_TOP, dialogDisplay.left, dialogDisplay.top, 0, 0, SWP_NOSIZE)) {
        m_dwLastError = GetLastError ();
    }

    return m_hDlg;
}

int CDialogTabPane::OnWmNotify (LPNMHDR    pNmhdr)
{
    int currentSel = TabCtrl_GetCurSel(pNmhdr->hwndFrom);
    switch (pNmhdr->code)
    {
        case TCN_SELCHANGING:
            if (currentSel == m_iTab)
                ::ShowWindow (m_hDlg, SW_HIDE);
            break;
        case TCN_SELCHANGE:
            if (currentSel == m_iTab)
                ::ShowWindow (m_hDlg, SW_SHOW);
            break;
    }

    return 0;
}

The dialog box resources used for the individual tab control tab panes are similar and look like:

IDD_DIALOG_TAB01 DIALOGEX 0, 0, 186, 115
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    LTEXT           "First tab",IDC_STATIC,68,7,96,14
    CONTROL         "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,21,150,13
    CONTROL         "Check2",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,36,150,13
    CONTROL         "Check3",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,53,150,13
    CONTROL         "Check4",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,69,150,13
    CONTROL         "Check5",IDC_CHECK5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,84,150,13
END

And the resource for the actual dialog containing the tab control looks like:

IDD_DIALOG_TAB00 DIALOGEX 0, 0, 336, 179
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog Tab Test"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,279,7,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,279,24,50,14
    CONTROL         "",IDC_TAB1,"SysTabControl32",0x0,47,35,226,137
END

Displaying dialog box with tab 1 selected and then tab 3 selected.

screen shot of dialog box with tab 1 selected

screen shot of dialog box with tab 3 selected

answered on Stack Overflow Nov 1, 2018 by Richard Chambers

User contributions licensed under CC BY-SA 3.0