<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href='base.xsl'?>

<desc title="オーナードローボタン">

	<image file='Exp_OwnerDraw1_1.png' width="400" height="200" alt="サンプルのスクリーンショット"/><r/>

	<r/>

	main.c
	<source>
#include &lt;stdio.h&gt;
#include &lt;windows.h&gt;
#include "library.h"

#define CLASS_NAME		"Owner Draw Sample"

enum BUTTON_STATE
{
	BG = 0,
	NORMAL,
	MOVER,
	GRAY,
	HIT,
	IMAGE_COUNT
};

HWND    _main          = NULL;
HWND    _button        = NULL;
SHORT   _button_left   = 0;
SHORT   _button_top    = 0;
WORD    _button_width  = 200;
WORD    _button_height = 30;
BOOL    _enable        = TRUE;
BOOL    _mouse_over    = FALSE;
BOOL    _hit           = FALSE;
HDC     _imageDC[IMAGE_COUNT];
HDC     _maskDC[IMAGE_COUNT];
HBITMAP _image[IMAGE_COUNT];
HBITMAP _mask[IMAGE_COUNT];

void _DeleteImage( void )
{
	int i;
	for( i = 0; i &lt; IMAGE_COUNT; i++ )
	{
		MyDeleteBitmap( _imageDC[i], _image[i] );
		MyDeleteBitmap( _maskDC[i], _mask[i] );
	}
}

BOOL _LoadImage( void )
{
	char * namelist[] = { "normal.bmp", "over.bmp", "gray.bmp", "hit.bmp" };
	BOOL failed = FALSE;
	int  i;
	for( i = 1; i &lt; IMAGE_COUNT; i++ )
	{
		if( !MyLoadBitmap( _main, &amp;_imageDC[i], &amp;_image[i], namelist[i-1] ) ) failed = TRUE;
		if( !MyMakeMask( RGB(255,0,255), _imageDC[i], _image[i], &amp;_maskDC[i], &amp;_mask[i] ) ) failed = TRUE;
	}
	if( failed )
	{
		_DeleteImage();
		return FALSE;
	}
	return TRUE;
}

LRESULT _OnButtonPaint( void )
{
	PAINTSTRUCT       ps;
	enum BUTTON_STATE state = GRAY;
	BeginPaint( _button, &amp;ps );
	if( _enable )
	{
		if( _hit ) state = HIT;
		else if( _mouse_over ) state = MOVER;
		else state = NORMAL;
	}
	BitBlt( ps.hdc, 0, 0, _button_width, _button_height, _imageDC[(int)BG], 0, 0, SRCCOPY );/*背景*/
	BitBlt( ps.hdc, 0, 0, _button_width, _button_height, _maskDC[(int)state], 0, 0, SRCAND );/*ボタンのマスク*/
	BitBlt( ps.hdc, 0, 0, _button_width, _button_height, _imageDC[(int)state], 0, 0, SRCPAINT );/*ボタンの絵*/
	EndPaint( _button, &amp;ps );
	return 1;
}

LRESULT _OnRightMouseButtonDown( void )
{
	if( _enable )
	{
		_enable = _mouse_over = _hit = FALSE;
		SetWindowLong( _button, GWL_STYLE, WS_CHILD|WS_DISABLED|WS_VISIBLE|BS_OWNERDRAW );
		InvalidateRect( _button, NULL, TRUE );
	}
	else
	{
		_enable = TRUE;
		SetWindowLong( _button, GWL_STYLE, WS_CHILD|WS_VISIBLE|BS_OWNERDRAW );
		InvalidateRect( _button, NULL, TRUE );
	}
	return 0;
}

LRESULT _OnPaint( void )
{
	PAINTSTRUCT ps;
	char        text[256];
	BeginPaint( _main, &amp;ps );
	sprintf( text, "ボタン外を右クリックで" );
	TextOut( ps.hdc, 0, 0, text, (int)strlen(text) );
	sprintf( text, "ボタンの有効・無効を切り替えます。" );
	TextOut( ps.hdc, 0, 20, text, (int)strlen(text) );
	EndPaint( _main, &amp;ps );
	return 0;
}

LRESULT CALLBACK ButtonProc( HWND h, UINT m, WPARAM w, LPARAM l )
{
	switch( m )
	{
	case WM_PAINT:		return _OnButtonPaint();
	case WM_MOUSEMOVE:	/*フォールスルー*/
	case WM_SETFOCUS:
		if( !_mouse_over )
		{
			_mouse_over = TRUE;
			InvalidateRect( _button, NULL, TRUE );
		}
		return 0;
	case WM_KILLFOCUS:
		if( _mouse_over )
		{
			_mouse_over = _hit = FALSE;
			InvalidateRect( _button, NULL, TRUE );
		}
		return 0;
	case WM_LBUTTONDOWN:
		if( !_hit )
		{
			_hit = TRUE;
			InvalidateRect( _button, NULL, TRUE );
		}
		return 0;
	case WM_LBUTTONUP:
		if( _hit )
		{
			_hit = FALSE;
			InvalidateRect( _button, NULL, TRUE );
		}
		return 0;
	}
	return DefWindowProc( h, m, w, l );
}

BOOL _CreateButton( void )
{
	RECT	r;
	HDC		hdc;

	if( !GetClientRect( _main, &amp;r ) ) return FALSE;

	_button_left = ( r.right - r.left - _button_width  ) / 2;
	_button_top  = ( r.bottom - r.top - _button_height ) / 2;

	if( !MyMakeBitmap( _main, &amp;_imageDC[0], &amp;_image[0], _button_width, _button_height ) ) return FALSE;

	hdc = GetDC( _main );
	BitBlt( _imageDC[0], 0, 0, _button_width, _button_height, hdc, 0, 0, SRCCOPY );
	ReleaseDC( _main, hdc );

	_button = CreateWindow( "BUTTON", "",
		WS_CHILD|WS_VISIBLE|BS_OWNERDRAW,
		_button_left, _button_top, _button_width, _button_height,
		_main, NULL,
		(HINSTANCE)GetWindowLong( _main, GWL_HINSTANCE ),
		NULL
		);

	if( !_button || !_LoadImage() )
	{
		_DeleteImage();
		return FALSE;
	}

	SetWindowLong( _button, GWL_WNDPROC, (long)ButtonProc );

	return TRUE;
}

LRESULT CALLBACK WinProc( HWND h, UINT m, WPARAM w, LPARAM l )
{
	switch( m )
	{
	case WM_RBUTTONDOWN: return _OnRightMouseButtonDown();
	case WM_PAINT:       return _OnPaint();
	case WM_DESTROY:
		_DeleteImage();
		PostQuitMessage(0);
		return 0;
	case WM_MOUSEMOVE:
		if( _mouse_over )
		{
			_mouse_over = _hit = FALSE;
			InvalidateRect( _button, NULL, TRUE );
		}
		return 0;
	}
	return DefWindowProc( h, m, w, l );
}

int WINAPI WinMain( HINSTANCE hinstance, HINSTANCE h, LPSTR c, int s )
{
	WNDCLASS	wc;
	MSG			msg;

	(void)h;
	(void)c;
	(void)s;

	memset( &amp;wc, 0, sizeof(wc) );
	wc.hbrBackground	= (HBRUSH)GetStockObject( BLACK_BRUSH );
	wc.hCursor			= LoadCursor( NULL, IDC_ARROW );
	wc.hIcon			= LoadIcon( NULL, IDI_WINLOGO );
	wc.hInstance		= hinstance;
	wc.lpfnWndProc		= WinProc;
	wc.lpszClassName	= CLASS_NAME;
	if( !RegisterClass( &amp;wc ) ) return 1;

	_main = CreateWindow( CLASS_NAME, "",
		WS_BORDER|WS_CAPTION|WS_CLIPCHILDREN|WS_SYSMENU|WS_VISIBLE,
		0, 0, 400, 200, NULL, NULL, hinstance, NULL
		);
	if( !_main ) return 1;
	if( !_CreateButton() ) return 1;

	while( GetMessage( &amp;msg, 0, 0, 0 ) &gt; 0 )
	{
		TranslateMessage( &amp;msg );
		DispatchMessage( &amp;msg );
	}
	return 0;
}
	</source>

	<r/>

	library.h
	<source>
#ifndef _my_library_h_
#define _my_library_h_

#include &lt;windows.h&gt;

extern void MyDeleteBitmap( HDC, HBITMAP );
extern BOOL MyLoadBitmap( HWND, HDC *, HBITMAP *, LPCSTR );
extern BOOL MyMakeBitmap( HWND, HDC *, HBITMAP *, DWORD, DWORD );
extern BOOL MyMakeMask( COLORREF, HDC, HBITMAP, HDC *, HBITMAP * );

#endif
	</source>

	<r/>

	library.c
	<source>
#include "library.h"

void MyDeleteBitmap( HDC memdc, HBITMAP bmp )
{
	if( !memdc || !bmp ) return;
	DeleteDC( memdc );
	DeleteObject( bmp );
}

BOOL MyLoadBitmap( HWND hwnd, HDC *memdc, HBITMAP *bmp, LPCSTR filename )
{
	HDC hdc;
	if( !hwnd || !memdc || !bmp || !filename ) return FALSE;
	hdc = GetDC( hwnd );
	*memdc = CreateCompatibleDC( hdc );
	*bmp = (HBITMAP)LoadImage( NULL, filename, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE );
	if( !*bmp ) return FALSE;
	SelectObject( *memdc, *bmp );
	ReleaseDC( hwnd, hdc );
	return TRUE;
}

BOOL MyMakeBitmap( HWND hwnd, HDC *memdc, HBITMAP *bmp, DWORD width, DWORD height )
{
	BITMAPINFOHEADER bi;
	RGBTRIPLE        *rgb;
	HDC              hdc
	if( !hwnd || !memdc || !bmp || !width || !height ) return FALSE;
	hdc = GetDC( hwnd );
	*memdc = CreateCompatibleDC( hdc );
	ZeroMemory( &amp;bi, sizeof(bi) );
	bi.biSize        = sizeof(bi);
	bi.biWidth       = width;
	bi.biHeight	     = height;
	bi.biPlanes      = 1;
	bi.biBitCount    = 24;
	bi.biCompression = BI_RGB;
	*bmp = CreateDIBSection( hdc, (BITMAPINFO*)&amp;bi, DIB_RGB_COLORS, (void**)(&amp;rgb), NULL, 0 );
	if( !*bmp ) return FALSE;
	SelectObject( *memdc, *bmp );
	ReleaseDC( hwnd, hdc );
	return TRUE;
}

BOOL MyMakeMask( COLORREF transparent_color, HDC hdc, HBITMAP bmp, HDC * maskdc, HBITMAP * mask )
{
	BITMAP   bitmap;
	COLORREF bg_color_old  = CLR_INVALID;
	HDC      white_maskdc  = NULL;
	HBITMAP  white_mask    = NULL;
	HDC      black_maskdc  = NULL;
	HBITMAP  black_mask    = NULL;
	BOOL	 result        = FALSE;
	if( !hdc || !bmp || !maskdc || !mask ) return FALSE;
	do
	{
		if( !GetObject( bmp, sizeof(bitmap), &amp;bitmap ) ) break;
		bg_color_old = SetBkColor( hdc, transparent_color );
		if( bg_color_old == CLR_INVALID ) break;

		white_maskdc = CreateCompatibleDC( hdc );
		white_mask   = CreateBitmap( bitmap.bmWidth, bitmap.bmHeight, 1, 1, NULL );
		if( !white_maskdc || !white_mask ) break;
		if( !SelectObject( white_maskdc, white_mask ) ) break;
		BitBlt( white_maskdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdc, 0, 0, SRCCOPY );

		black_maskdc = CreateCompatibleDC( hdc );
		black_mask   = CreateCompatibleBitmap( hdc, bitmap.bmWidth, bitmap.bmHeight );
		if( !black_maskdc || !black_mask ) break;
		if( !SelectObject( black_maskdc, black_mask ) ) break;
		BitBlt( black_maskdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, white_maskdc, 0, 0, NOTSRCCOPY );

		if( SetBkColor( hdc, bg_color_old ) == CLR_INVALID ) break;
		BitBlt( hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, black_maskdc, 0, 0, SRCAND );

		*maskdc = white_maskdc;
		*mask   = white_mask;
		result  = TRUE;
	}
	while(0);
	MyDeleteBitmap( black_maskdc, black_mask );
	if( !result )
	{
		MyDeleteBitmap( white_maskdc, white_mask );
		*maskdc = NULL;
		*mask   = NULL;
	}
	return result;
}
	</source>

	<r/>

	<font color="red">※08/07/08修正</font><r/>

	<block>

		記事をアップロードした直後、
		オーナードローボタンのとても参考になる実装方法を説明されているサイトを発見しました。<r/>

		<list>
			<item>
				その10 目指せ究極のカスタムボタン！<r/>
				<link url='http://marupeke296.com/TIPS_No10_CustomButton.html'>http://marupeke296.com/TIPS_No10_CustomButton.html</link>
			</item>
		</list>

		上記を参考にサンプルソースも変更し、
		動作確認が取れた上で記事の内容も修正しました。<r/>

	</block>

	サンプルソースについて

	<block>

		ソースコードがとても長くなってしまったので、
		ファイルを分割しました。<r/>
		library.c に書かれている関数は、他の項で紹介している私が作成した関数です。<r/>
		関数名、引数名、内部で使用している変数名と、
		多少コードを修正していますが、
		戻り値と関数の処理内容は変えていません。<r/>


		再利用した関数を扱っている項。
		<list>
			<item>
				<link url='API_Bitmap.htm'>ビットマップを表示させる</link>
			</item>
			<item>
				<link url='API_Bitmap5.htm'>ビットマップを表示させる 5</link>
			</item>
			<item>
				<link url='Exp_CreateMask.htm'>元の絵からマスクを作る</link>
			</item>
		</list>

		library.c の関数については他の項で説明してますので、
		ここで重要なのは main.c の内容となります。<r/>

	</block>

	サンプルの実行に必要な画像データ

	<block>

		以下のファイルを実行ファイルと同じフォルダにコピーします。

		<list>
			<item>
				<link url='images/normal.bmp'>normal.bmp</link>
			</item>
			<item>
				<link url='images/gray.bmp'>gray.bmp</link>
			</item>
			<item>
				<link url='images/over.bmp'>over.bmp</link>
			</item>
			<item>
				<link url='images/hit.bmp'>hit.bmp</link>
			</item>
		</list>

	</block>

	コンパイルした環境

	<block>

		<list>
			<item>
				Windows XP Home Edition
			</item>
			<item>
				Visual C++ 2003<r/>
				ウィンドウズアプリケーションとしてプロジェクトを作成。<r/>
				プロジェクトの設定はデフォルト。
			</item>
			<item>
				cygwin の cc(version 3.4.4)<r/>
				-mwindows オプション付きでコンパイル。
			</item>
			<item>
				bcc32(version 5.5)<r/>
				<link url='http://www.hi-ho.ne.jp/jun_miura/bccdev.htm'>BCC Developer</link>
				でプロジェクトを作成。<r/>
				ANSI C 準拠オプション付きでコンパイル。
			</item>
		</list>

	</block>

	オーナードローボタン

	<block>

		最近のアプリケーションで使われているボタンはグラフィカルになり、
		ウィンドウズのデフォルトのデザインではなく、
		専用の絵が使われているものが増えました。<r/>

		<r/>

		マウスカーソルがボタンに触るとちょっと明るくなったり、
		ボタンをクリックするとまた専用の絵に変わったりします。<r/>
		そういったボタンを作る場合、
		Win32 API の入門書や入門サイトには、
		「オーナードローボタンを使う」と書かれているのを良く目にします。<r/>

		<r/>

		ウィンドウズのボタンコントロールには（ボタンだけではありませんが）、
		オーナードローという機能があります。<r/>
		オーナードローを使わない場合、
		ボタンなどのコントロールの描画はウィンドウズが行います。<r/>
		ウィンドウズにまかせてしまえば、
		プログラムする側が描画処理を書く必要はありません。<r/>

		<r/>

		オーナードローは、プログラムする側が自分で描画処理を書かなければいけません。<r/>
		従って、自分でデザインした絵を自分で描画して、
		よりグラフィカルなボタンを表示することができます。<r/>

	</block>

	作りたいボタン

	<block>

		最近よくあるグラフィカルなボタンの動作を以下に列挙します。<r/>

		<list>
			<item>ビットマップを読み込んで表示。</item>
			<item>マウスカーソルがボタンに触れたら絵を変える。</item>
			<item>マウスカーソルがボタンから離れたら変える前の絵に戻す。</item>
			<item>ボタンの上で左マウスボタンが押されたら絵を変える。</item>
			<item>↑の後、左ボタンが解放されたら変える前の絵に戻す。</item>
			<item>ボタンが無効化されたら絵を変える。</item>
		</list>

	</block>

	サブクラス化

	<block>

		前述したサイトに書かれていることですが、
		ボタンをサブクラス化することで、
		ボタンのメッセージを自分で処理できるようになります。<r/>
		サブクラス化しないと、ボタンの上でマウスカーソルが動いた、
		ボタンの上でマウスの左ボタンが押された・離されたなどのメッセージが取得できません。<r/>

		<r/>

		ボタンの上にマウスカーソルがあるときは、
		ボタンのメッセージ処理関数(ButtonProc)が呼ばれ、
		ボタンからマウスカーソルが離れているときは、
		ウィンドウのメッセージ処理関数(WndProc)が呼ばれます。<r/>
		これを利用することで、
		ボタンの上にマウスカーソルが乗った・降りたが分かります。<r/>

	</block>

	ボタンを透明にするには

	<block>

		前述したサイトでは ButtonProc の中で WM_ERASEBKGND を捕捉して
		return 1 を返せば、
		ボタンが透けるようになると書かれていますが、
		私の環境ではそのような結果にはなりませんでした。<r/>
		おそらく、リージョンを設定しない限りボタンは透けません。<r/>

		<r/>

		ボタンの状態によって、透ける部分が変わるような場合、
		その分リージョンを作って保持しておかないとなりません。<r/>
		サンプルの場合は、
		通常、マウスオーバー、ヒット、無効の４つのリージョンが必要です。<r/>
		それは少し面倒ですので、
		別の方法で背景が透けるようにしています。<r/>

		<r/>

		サンプルで行っている方法は、
		予めボタンを表示する範囲のクライアント領域を、
		ビットマップに保存しておきます。<r/>
		そしてボタンを表示する際(WM_PAINTが呼ばれたとき)に、
		保存しておいた背景を描画し、
		その上にボタンの絵を重ねて描画します。<r/>
		こうすれば、リージョンを作らなくて済む上、
		ボタンの背景も透けて見えるようになります。

	</block>

	参考にしたサイト
	<list>
		<item>
			ウィンドウメッセージ一覧<r/>
			<link url='http://www.winapi-database.com/Message/WM/'>http://www.winapi-database.com/Message/WM/</link>
		</item>
		<item>
			MSDN Button<r/>
			<link url='http://msdn.microsoft.com/ja-jp/library/bb775943(en-us).aspx'>http://msdn.microsoft.com/ja-jp/library/bb775943(en-us).aspx</link>
		</item>
		<item>
			MSDN Button Styles<r/>
			<link url='http://msdn.microsoft.com/ja-jp/bb775951(en-us).aspx'>http://msdn.microsoft.com/ja-jp/bb775951(en-us).aspx</link>
		</item>
		<item>
			その10 目指せ究極のカスタムボタン！<r/>
			<link url='http://marupeke296.com/TIPS_No10_CustomButton.html'>http://marupeke296.com/TIPS_No10_CustomButton.html</link>
		</item>
	</list>

</desc>

