半透明ウィンドウ、部分的に不透明

半透明だけど部分的に不透明にしたい、という場合、ウィンドウをふたつ作成して、常に連動するように制御する必要がある、ということがわかりました。

ポイントは、半透明のウィンドウと指定色透明のウィンドウをふたつ作成し、常に連動して動くようにすることなんですね。

親ウィンドウは半透明で、こちらはマウスイベントを受け取れます。

子ウィンドウは SetLayeredWindowAttributes で指定した色が透明になるように設定して、その色で背景を塗りつぶせば、それ以外の色は不透明になる、という具合です。

このサンプルの全ソースは以下のとおりです。

// 下で指定された定義の前に対象プラットフォームを指定しなければならない場合、以下の定義を変更してください。
// 異なるプラットフォームに対応する値に関する最新情報については、MSDN を参照してください。
#ifndef WINVER				// Windows XP 以降のバージョンに固有の機能の使用を許可します。
#define WINVER 0x0501		// これを Windows の他のバージョン向けに適切な値に変更してください。
#endif

#ifndef _WIN32_WINNT		// Windows XP 以降のバージョンに固有の機能の使用を許可します。                   
#define _WIN32_WINNT 0x0501	// これを Windows の他のバージョン向けに適切な値に変更してください。
#endif						

#ifndef _WIN32_WINDOWS		// Windows 98 以降のバージョンに固有の機能の使用を許可します。
#define _WIN32_WINDOWS 0x0410 // これを Windows Me またはそれ以降のバージョン向けに適切な値に変更してください。
#endif

#ifndef _WIN32_IE			// IE 6.0 以降のバージョンに固有の機能の使用を許可します。
#define _WIN32_IE 0x0600	// これを IE. の他のバージョン向けに適切な値に変更してください。
#endif

//#define WIN32_LEAN_AND_MEAN		// Windows ヘッダーから使用されていない部分を除外します。
// Windows ヘッダー ファイル
#include <windows.h>
#include <gdiplus.h>

// C ランタイム ヘッダー ファイル
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>

#define PARENT_WINDOW TEXT("PartTransparent")
#define CHILD_WINDOW TEXT("PartTransparentChield")
#define TRANSPARENT_COLOR RGB(1, 2, 3)

ATOM MyRegisterClass(WNDPROC lpfnWndProc, LPTSTR szWindowClass, HBRUSH hbr) {
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style			= 0;
	wcex.lpfnWndProc	= lpfnWndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= (HINSTANCE) GetModuleHandle(NULL);
	wcex.hIcon			= NULL;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= hbr;
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= NULL;
	return RegisterClassEx(&wcex);
}

LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
	switch (message) {
	case WM_PAINT:
		{
			RECT rect;
			GetClientRect(hWnd, &rect);

			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hWnd, &ps);
			FillRect(hdc, &rect, CreateSolidBrush(TRANSPARENT_COLOR));
			Gdiplus::Graphics g(hdc);
			Gdiplus::Image *img = new Gdiplus::Image(TEXT("image.png"));
			UINT width = img->GetWidth();
			g.DrawImage(img, rect.left, rect.top, rect.right, rect.bottom);
			EndPaint(hWnd, &ps);
			delete img;
		}
		break;
	case WM_WINDOWPOSCHANGED:
		{
			LPWINDOWPOS lpwinpos = reinterpret_cast<LPWINDOWPOS>(lParam);
			HWND hwnd = lpwinpos->hwnd;
			lpwinpos->hwnd = GetParent(hWnd);
			SendMessage(lpwinpos->hwnd, WM_WINDOWPOSCHANGED, NULL, reinterpret_cast<LPARAM>(lpwinpos));
			lpwinpos->hwnd = hwnd;
		}
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

LRESULT CALLBACK WndProcParent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
	static HWND hWndChild = NULL;

	switch (message) {
	case WM_CREATE:
		MyRegisterClass(WndProcChild, CHILD_WINDOW, CreateSolidBrush(TRANSPARENT_COLOR));
		hWndChild = CreateWindowEx(
			WS_EX_LAYERED | WS_EX_TRANSPARENT,
			CHILD_WINDOW,
			CHILD_WINDOW,
			WS_POPUP | WS_VISIBLE,
			0,
			0,
			0,
			0,
			hWnd,
			NULL,
			(HINSTANCE) GetModuleHandle(NULL),
			NULL);

		MoveWindow(hWndChild, 150, 150, 100, 100, TRUE);
		SetLayeredWindowAttributes(hWndChild, TRANSPARENT_COLOR, 0, LWA_COLORKEY);
		ShowWindow(hWndChild, SW_SHOW);
		UpdateWindow(hWndChild);
		break;
	case WM_MOVE:
		// 子ウィンドウを制御
		if (hWndChild != NULL) {
			RECT rect;
			GetWindowRect(hWnd, &rect);
			rect.left += 50;
			rect.top += 50;
			rect.right -= 50;
			rect.bottom -= 50;
			MoveWindow(hWndChild, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
		}
		break;
	case WM_LBUTTONDOWN:
		// ウィンドウを移動できるようにするトリック
		SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
		break;
	case WM_RBUTTONDOWN:
		// 右クリックで終了
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);
	Gdiplus::GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

	MSG msg;
	HACCEL hAccelTable;

	MyRegisterClass(WndProcParent, PARENT_WINDOW, (HBRUSH) GetStockObject(BLACK_BRUSH));

	HWND hWnd = CreateWindowEx(
		WS_EX_LAYERED | WS_EX_TOOLWINDOW,
		PARENT_WINDOW,
		PARENT_WINDOW,
		WS_POPUP | WS_VISIBLE,
		0,
		0,
		0,
		0,
		NULL,
		NULL,
		hInstance,
		NULL);

	MoveWindow(hWnd, 100, 100, 200, 200, TRUE);
	SetLayeredWindowAttributes(hWnd, 0, 196, LWA_ALPHA);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	hAccelTable = LoadAccelerators(hInstance, PARENT_WINDOW);

	while (GetMessage(&msg, NULL, 0, 0)) {
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	Gdiplus::GdiplusShutdown(gdiplusToken);
	return static_cast<int>(msg.wParam);
}

下記に Visual C++ 2005 のプロジェクトを置いてあります。

プログラムの終了は起動したウィンドウを右クリックしてください。

一点わからない点があります。

指定色透明を SetLayeredWindowAttributes で行うわけですが、今回のように任意の画像を表示させる場合、その画像に透明色に指定した色が含まれていた場合、以下のように情けない状態になってしまいます。

前もって調べられればそれに越したことはないのですが、プログラムを起動した後から動的に画像が変更される場合、そういうわけにもいきません。いったいどうすればいいのでしょう?

さて最後に、このサンプルを作成するにあたりものすごく参考になったのが、以下のブログエントリです。

背景が透明なウィンドウの作り方(2/2)

上記は MFC を使用して作られているのですが、僕は Win32 API が好きなのです。