EternalWindows
タブコントロール / タブの削除

タブコントロールに追加されたタブを削除するには、TabCtrl_DeleteItemを呼び出します。

BOOL TabCtrl_DeleteItem(
  HWND hwnd,
  int iItem
);

hwndは、タブコントロールのハンドルを指定します。 iItemは、削除するアイテムのインデックスを指定します。

タブの削除はどのようなときに行われるものでしょうか。 たとえば、マウスの右クリックからポップアップメニューを表示し、 そこで削除を選択した場合にタブを削除する場合などが考えられます。 このような場合、座標位置からタブのインデックスを取得するTabCtrl_HitTestが役に立ちます。

int TabCtrl_HitTest(
  HWND hwnd,
  LPTCHITTESTINFO pinfo
);

hwndは、タブコントロールのハンドルを指定します。 pinfoは、TCHITTESTINFO構造体のアドレスを指定します。 戻り値は、タブのインデックスが返ります。

今回のプログラムは、タブ上で右クリックが行われた場合にそのタブを削除します。

#include <windows.h>
#include <commctrl.h>

#pragma comment (lib, "comctl32.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndTab = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		TCITEM               item;
		INITCOMMONCONTROLSEX ic;
		
		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_TAB_CLASSES;
		InitCommonControlsEx(&ic);
		
		hwndTab = CreateWindowEx(0, WC_TABCONTROL, TEXT(""), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);		
		
		item.mask    = TCIF_TEXT | TCIF_PARAM;
		item.pszText = TEXT("タブA");
		item.lParam  = 0;
		TabCtrl_InsertItem(hwndTab, 0, &item);
		
		item.mask    = TCIF_TEXT | TCIF_PARAM;
		item.pszText = TEXT("タブB");
		item.lParam  = 1;
		TabCtrl_InsertItem(hwndTab, 1, &item);

		return 0;
	}
	
	case WM_PAINT: {
		int         yStart;
		HDC         hdc;
		RECT        rc;
		PAINTSTRUCT ps;
		TCITEM      item;

		hdc = BeginPaint(hwnd, &ps);
		
		GetClientRect(hwndTab, &rc);
		yStart = rc.bottom - rc.top;

		item.mask = TCIF_PARAM;
		TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &item);
		
		if (item.lParam == 0)
			Rectangle(hdc, 0, yStart, 30, yStart + 30);
		else
			Ellipse(hdc, 0, yStart, 30, yStart + 30);

		EndPaint(hwnd, &ps);

		return 0;
	}
	
	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == TCN_SELCHANGE)
			InvalidateRect(hwnd, NULL, TRUE);
		else if (((LPNMHDR)lParam)->code == NM_RCLICK) {
			int           nIndex;
			TCHITTESTINFO info;

			GetCursorPos(&info.pt);
			ScreenToClient(hwndTab, &info.pt);
			
			nIndex = TabCtrl_HitTest(hwndTab, &info);
			if (nIndex != -1) {
				TabCtrl_DeleteItem(hwndTab, nIndex);
				TabCtrl_SetCurSel(hwndTab, 0);
				InvalidateRect(hwnd, NULL, TRUE);
			}
		}
		else
			;
		return 0;
	
	case WM_SIZE:
		MoveWindow(hwndTab, 0, 0, LOWORD(lParam), 30, TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

今回のTabCtrl_InsertItemの呼び出しでは、TCITEM構造体のlParamにタブの番号を指定しています。 この狙いは、タブ毎に行う描画を明確に区別するためであり、 前節のようなTabCtrl_GetCurSelによる分岐を避けることが目的です。 たとえば、タブAが選択されている場合は、TabCtrl_GetCurSelは0を返しますが、 タブAが削除されてタブBが選択された場合でも、TabCtrl_GetCurSelは0を返します。 理由は、タブAの削除によってタブBが先頭のタブになったからです。 つまり、タブのインデックスというのは常に変化するため、 これを頼りに呼び出すべき描画関数を決定してはいけません。

タブコントロール上で右クリックをした場合は、通知コードがNM_RCLICKであるWM_NOTIFYが送られます。 今回はここでタブの削除を行います。

else if (((LPNMHDR)lParam)->code == NM_RCLICK) {
	int           nIndex;
	TCHITTESTINFO info;

	GetCursorPos(&info.pt);
	ScreenToClient(hwndTab, &info.pt);
	
	nIndex = TabCtrl_HitTest(hwndTab, &info);
	if (nIndex != -1) {
		TabCtrl_DeleteItem(hwndTab, nIndex);
		TabCtrl_SetCurSel(hwndTab, 0);
		InvalidateRect(hwnd, NULL, TRUE);
	}
}

クリックされた位置から対応するアイテムのインデックスを取得するには、 TabCtrl_HitTestを呼び出すことになります。 これには、TCHITTESTINFO構造体のptにカーソルの位置をクライアント座標で指定しておきます。 返されたインデックスが-1でない場合は、何らかのタブをクリックしたことを意味するため、 そのタブをTabCtrl_DeleteItemで削除します。 また、この時点で選択状態のタブがなくなるため、TabCtrl_SetCurSelで先頭のタブを選択しておきます。 InvalidateRectの呼び出しにより、再描画が行われることになります。

今回のWM_PAINTでは、現在選択されているタブからタブ番号を取得しています。 これは、lParamに格納していましたから、lParamが初期化されるようmaskにTCIF_PARAMを指定し、 TabCtrl_GetItemを呼び出します。 取得した番号が0である場合は、間違いなく現在選択されているタブがタブAであると分かります。


戻る