概要

タブコントロールは、下図のようにタブにより画面(ページ)を切り替えて使用するものです。
これらの表示は、メインダイアログにタブと連動する子ダイアログを貼り付けてそのうち1つのダイアログのみ表示されるというトリッキーな実装が標準的です。
下図では名前タブと属性タブにより切り替えています。

ダイアログにリソースでタブコントロールのみを作成しTabCtrl_InsertItemマクロでタブを追加する下図のようになります。

ダイアログボックスを親とするそれぞれのタブ用の子ダイアログをCreateDialog APIで作成します。
それぞれの子ダイアログはリソースで定義され下図の通りです。

子ウィンドウのスタイルはリソースで
STYLE WS_BORDER | WS_CHILD
のように定義しています。子ウィンドウにタイトルはつけてはいけません。
後は、子ダイアログのいずれかの表示を有効にすれば、初期表示は完成します。
タブがクリックされるとTCN_SELCHANGEメッセージが発生するので選択されたタブの表示を有効にしてタブの切り替えを実装します。
子ウィンドウは、タブコントロールの大きさに合わせてプログラムで大きさを変更しています。
各ウィンドウの親子関係は以下の通りです。
""に囲まれた最初の文字列がウィンドウタイトル、次の文字列がウィンドウクラス名です。
メインダイアログ
├"" "SysTabControl32"
│  ├ "山田太郎" "Static"
│  ├ "男"       "Static"
│  ├ "子供"     "Static"
│  ├ "OK"       "Button"
│  └ "キャンセル"    "Button"
│
├"" "#32770"
│  └ "山田太郎" "Edit"
│
└"" "#32770"
    ├ "性別"    "Button"
    ├ "男"      "Button"
    ├ "女"      "Button"
    ├ "料金"    "Button"
    ├ "子供"    "Button"
    └ "大人"    "Button"

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

プロジェクトの作成

Win32プロジェクト Windowsアプリケーション

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

プログラムソースの概要

_tWinMain関数

Windowsから最初に_tWinMain関数が呼び出されます。 DialogBox APIよりメインダイアログボックスを作成します。

DlgProc1ダイアログボックスのプロシージャー

メインダイアログボックスのプロシージャーです。

case WM_INITDIALOG:

ダイアログボックスの初期化時に呼び出されます。
GetDlgItem APIによりタブコントロールのウィンドウハンドルを取得します。
TabCtrl_InsertItemマクロによりタブを追加します。
CreateDialog APIにより子ダイアログを作成します。
子ダイアログの最初の1個目を表示します。
子ダイアログの大きさをタブコントロールに合わせるためGetClientRect APIによりメインダイアログボックスのクライアントサイズを取得し、SendMessage APIによりWM_SIZEメッセージを作成します。

case WM_SIZE:

ダイアログボックスの大きさが変更されたときに呼び出されます。
ダイアログボックスの大きさは、ダイアログボックスのスタイルで変更できないようにしていますが、前項のとおり、子ウィンドウの大きさを調整するために、WM_SIZEメッセージの処理を行います。
リソース側でうまく大きさを調整しても環境が変わるとウィンドウ枠の大きさが変わったりするのでプログラムで処理するのが常道です。
通常は、メインダイアログのクライアント領域一杯にタブコントロールは配置されますが、今回は小さくしています。
通常であれば、タブコントロールのクライアント領域の大きさを取得し、TabCtrl_AdjustRectマクロにより子ウィンドウの大きさを算出し、MoveWindow APIにより大きさを変更します。
今回は、タブコントロールのメインウィンドウ上でのクライアント領域の大きさを求めるためにGetClientRect APIを使用しますが、このAPIは左上座標が0,0で右下座標がウィンドウサイズとなります。
今回のケースでは、タブコントロールの左上のクライアント座標が0,0ではないので、メインダイアログの左上座標(0,0)とタブコントロール上での左上座標(0,0)をスクリーン座標に変換し、その差分をメインダイアログボックス上でのタブコントロールのクライアント領域の左上座標としています。
あとは、通常通りTabCtrl_AdjustRectマクロにより子ウィンドウの大きさを算出し差分を加算しています。

case WM_NOTIFY:

case TCN_SELCHANGE:
タブをクリックした場合に発生します。
TabCtrl_GetCurSelマクロによりクリックされたタブ番号を取得し、取得されたタブ番号の子ダイアログボックスの表示を有効にします。

case IDOK:

OKプッシュボタンのID番号がIDOKです。 OKプッシュボタンをクリックするとこのメッセージが発生します。 GetDlgItemText APIを呼び出し、エディットボックスの文字列を取得します。 MessageBox APIを呼び出し、取得した文字列を表示します。 EndDialog APIを呼び出し、ダイアログボックスを終了させます。 このAPIの第2引数は、ダイアログボックスが終了し、DialogBox APIが返す値となります。

case IDCANCEL:

キャンセルプッシュボタンのID番号がIDCANCELKです。 キャンセルプッシュボタンをクリックするとこのメッセージが発生します。 EndDialog APIを呼び出し、ダイアログボックスを終了させます。 このAPIの第2引数は、ダイアログボックスが終了し、DialogBox APIが返す値となります。

Page0Proc関数

子ダイアログ(ページ0)のプロシージャーです。

case WM_INITDIALOG:

エディットボックスの初期値を設定しています。

case WM_COMMAND:

エディットボックスの内容が変更された時に発生するEN_UPDATEメッセージを処理します。
エディットボックスの内容を取得し、親ダイアログボックスのラベルを変更します。
ラベルは親ダイアログの子ウィンドウです。子ダイアログも親ダイアログの子ウィンドウですので、親ウィンドウのハンドルをGetParen APIで取得しGetDlgItem APIを呼び出しています。

Page1Proc関数

子ダイアログ(ページ1)のプロシージャーです。

case WM_INITDIALOG:

ラジオボタンの初期値を設定しています。

case WM_COMMAND:

ラジオボタンの内容が変更された時に発生すBN_CLICKEDメッセージを処理します。
ラジオボタン内容により、親ダイアログボックスのラベルを変更します。
ラベルは親ダイアログの子ウィンドウです。子ダイアログも親ダイアログの子ウィンドウですので、親ウィンドウのハンドルをGetParen APIで取得しGetDlgItem APIを呼び出しています。

プログラムソース

tabctl.cpp

//	タブコントロールサンプル
//	Visual C++ 2013 32/64bit

#include <windows.h>
#include <commctrl.h> 
#include <stdio.h>
#include <tchar.h>
#include "resource.h"

//	ダイアログボックスプロシージャー
LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
//	タブコントロールの0ページ目
LRESULT CALLBACK Page0Proc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
//	タブコントロールの1ページ目
LRESULT CALLBACK Page1Proc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

HINSTANCE hInst;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,
                   TCHAR* lpszCmdLine, int nCmdShow){
	::hInst = hInstance;
	DialogBox(hInstance, TEXT("TAB_DLG"), 0, (DLGPROC)DlgProc1);

    return (int)0;
}

//	ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
	static HWND hTab;
	static HWND hPage0;
	static HWND hPage1;
	RECT rc;
	TCITEM tc_item;
    switch (msg) {
        case WM_INITDIALOG:
			hTab = GetDlgItem(hDlg, IDC_TABCNTRL);
			tc_item.mask = TCIF_TEXT;
			tc_item.pszText = _TEXT("名前");
			TabCtrl_InsertItem(hTab, 0, &tc_item);

			tc_item.pszText = TEXT("属性");
			TabCtrl_InsertItem(hTab, 1, &tc_item);

			hPage0 = CreateDialog(::hInst, _TEXT("DLG1"), hDlg, (DLGPROC)Page0Proc);
			hPage1 = CreateDialog(::hInst, _TEXT("DLG2"), hDlg, (DLGPROC)Page1Proc);
			ShowWindow(hPage0, SW_SHOW);
			ShowWindow(hPage1, SW_HIDE);
			GetClientRect(hDlg, &rc);
			SendMessage(hDlg, WM_SIZE, 0, MAKELPARAM(rc.right, rc.bottom));
			return TRUE;
		case WM_SIZE:{
			POINT dlg_pt;
			POINT tab_pt;
			dlg_pt.x = dlg_pt.y = 0;
			ClientToScreen(hDlg, &dlg_pt);
			tab_pt.x = tab_pt.y = 0;
			ClientToScreen(hTab, &tab_pt);

			GetClientRect(hTab, &rc);
			TabCtrl_AdjustRect(hTab, FALSE, &rc);

			int dx = tab_pt.x - dlg_pt.x;
			int dy = tab_pt.y - dlg_pt.y;
			MoveWindow(hPage0, rc.left+dx, rc.top+dy, rc.right - rc.left, rc.bottom - rc.top , TRUE);
			MoveWindow(hPage1, rc.left+dx, rc.top+dy, rc.right - rc.left, rc.bottom - rc.top, TRUE);
			break;
		}
		case WM_NOTIFY:
			switch (((NMHDR *)lParam)->code){
				case TCN_SELCHANGE:{	//	タブの切り替え
					int num=TabCtrl_GetCurSel(hTab);
					if (num == 0){
						ShowWindow(hPage0, SW_SHOW);
						ShowWindow(hPage1, SW_HIDE);
					}
					else{
						ShowWindow(hPage0, SW_HIDE);
						ShowWindow(hPage1, SW_SHOW);
					}
					break;
				}
			}
			return FALSE;
		case WM_COMMAND:
            switch (LOWORD(wParam)) {
				case IDOK:{
					EndDialog(hDlg, TRUE);
					return TRUE;
				}
				case IDCANCEL:
					EndDialog(hDlg,FALSE);
					return FALSE;
				default:
					return FALSE;
			}
			default:
				return FALSE;
	}
	return TRUE;
}

//	タブコントロールの0ページ目

LRESULT CALLBACK Page0Proc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
	TCHAR buf[32];
	switch (msg) {
	case WM_INITDIALOG:
		SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), _TEXT("山田太郎"));
		return TRUE;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDC_EDIT1:
			if (HIWORD(wParam) == EN_UPDATE){	//	エディットボックスが変更された場合
				GetDlgItemText(hDlg, IDC_EDIT1, (TCHAR*)buf, sizeof(buf) / sizeof(TCHAR));
				SetWindowText(GetDlgItem(GetParent(hDlg), IDC_LABEL1), buf);
			}
			break;
		default:
			return FALSE;
		}
	default:
		return FALSE;
	}
	return TRUE;
}

//	タブコントロールの1ページ目

LRESULT CALLBACK Page1Proc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
	switch (msg) {
	case WM_INITDIALOG:
		SendMessage(GetDlgItem(hDlg, IDC_RADIOBUTTON101), BM_SETCHECK, BST_CHECKED, 0);
		SendMessage(GetDlgItem(hDlg, IDC_RADIOBUTTON111), BM_SETCHECK, BST_CHECKED, 0);
		return TRUE;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDC_RADIOBUTTON101:
			if (HIWORD(wParam) == BN_CLICKED){
				SetWindowText(GetDlgItem(GetParent(hDlg), IDC_LABEL103), _TEXT("男"));
			}
			return FALSE;
		case IDC_RADIOBUTTON102:
			if (HIWORD(wParam) == BN_CLICKED){
				SetWindowText(GetDlgItem(GetParent(hDlg), IDC_LABEL103), _TEXT("女"));
			}
			return FALSE;
		case IDC_RADIOBUTTON111:
			if (HIWORD(wParam) == BN_CLICKED){
				SetWindowText(GetDlgItem(GetParent(hDlg), IDC_LABEL113), _TEXT("子供"));
			}
			return FALSE;
		case IDC_RADIOBUTTON112:
			if (HIWORD(wParam) == BN_CLICKED){
				SetWindowText(GetDlgItem(GetParent(hDlg), IDC_LABEL113), _TEXT("大人"));
			}
			return FALSE;

		default:
			return FALSE;
		}
	default:
		return FALSE;
	}
	return TRUE;
}

resource.h

#define IDC_TABCNTRL 300

#define IDC_EDIT1	200
#define IDC_LABEL1	210

#define	IDC_GROUPBOX100		100
#define	IDC_RADIOBUTTON101	101
#define	IDC_RADIOBUTTON102	102
#define	IDC_LABEL103		103

#define	IDC_GROUPBOX110		110
#define	IDC_RADIOBUTTON111	111
#define	IDC_RADIOBUTTON112	112
#define	IDC_LABEL113		113

resource.rc

#include <windows.h>
#include "resource.h"

TAB_DLG DIALOG DISCARDABLE 0, 0, 205, 185
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_SETFONT
CAPTION "タブコントロール"
FONT 9,  "MS Shell Dlg"
{
	CONTROL "", IDC_TABCNTRL, "SYSTABCONTROL32", WS_CHILD | WS_VISIBLE | TCS_SINGLELINE | TCS_TABS, 7, 7, 120, 120

	CONTROL			"" , IDC_LABEL1  ,"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY,134,7,64,14
	CONTROL "男", IDC_LABEL103, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 134, 27, 64, 14
	CONTROL "子供", IDC_LABEL113, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 134, 48, 64, 14

	DEFPUSHBUTTON   "OK", IDOK, 9, 154, 50, 14
	PUSHBUTTON      "キャンセル", IDCANCEL, 70, 154, 50, 14

}

DLG1 DIALOG DISCARDABLE  0, 0, 121, 76
STYLE WS_BORDER | WS_CHILD
FONT 9, "MS Shell Dlg"
{
	EDITTEXT        IDC_EDIT1, 7, 13, 104, 14, ES_AUTOHSCROLL
}

DLG2 DIALOG DISCARDABLE 0, 0, 129, 147
STYLE  WS_BORDER | WS_CHILD
FONT 9, "MS Shell Dlg"
{
	CONTROL "性別", IDC_GROUPBOX100, "BUTTON", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 7, 7, 85, 35
	CONTROL "男", IDC_RADIOBUTTON101, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP, 14, 21, 32, 14
	CONTROL "女", IDC_RADIOBUTTON102, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 53, 21, 32, 14

	CONTROL "料金", IDC_GROUPBOX110, "BUTTON", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 7, 56, 85, 35
	CONTROL "子供", IDC_RADIOBUTTON111, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP, 14, 70, 32, 14
	CONTROL "大人", IDC_RADIOBUTTON112, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 53, 70, 32, 14
}

ソースファイルと実行ファイルのダウンロード

ダウンロード tabctl.zip(38.9kByte)
ZIPファイルに含まれるファイル
tabctl.cpp
resource.h
resource.rc
tabctl.exe