FAQ8-1

Q. コントロールの背景を変えるには

 MFCで,プログラムを作成しています.初歩的な質問ですが,SDIアプリケーションの背景は白なので,OnDraw時に,バックをライトグレーにし,プログラム内で,各コントロールを作成しています.
 ところが,スタティック, チェックボックスの表示などは必ず背景が白ぬきになってしまいます.
 どのようにすれば,ライトグレーにすることができるのでしょうか?
(編注:この質問をしてくれたのは,山田正一さんです)


A. WM_CTLCOLORを使う

WM_CTLCOLORを使う

 コントロールの背景を変えるには,WM_CTLCOLORメッセージを使います.
 コントロールが描画されようとしているとき,コントロールは親ウィンドウに対してWM_CTLCOLORを送ってきます.
 そのとき描画方法などを決めてやれば背景を変えることができるようになっています.
 具体的には,リスト8-1のようにSetBkColor()を使って背景色を変えるか,SetBkMode()で背景モードを透明にするとかします(図8-1).

[図8-1] :

[リスト8-1] :

(起動後にやっておく)
	HBRUSH	hbrBack;
	hbrBack = CreateSolidBrush( GetSysColor(COLOR_BTNFACE) );

(Win16の場合)
	case WM_CTLCOLOR:
		if( LOWORD(lParam) == CTLCOLOR_BTN ||
			LOWORD(lParam) == CTLCOLOR_STATIC ) {
			SetBkColor( (HDC)wParam, GetSysColor(COLOR_BTNFACE) );
			return (long)hbrBack;
		} else {
			return DefWindowProc( hwnd , message ,wParam ,lParam );
		}
		

(Win32の場合)
	case WM_CTLCOLORBTN:
	case WM_CTLCOLORSTATIC:
		SetBkColor( (HDC)wParam, GetSysColor(COLOR_BTNFACE) );
		return (long)hbrBack;

(MFCの場合)
HBRUSH CTestView::OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor ) {
	if( nCtlColor == CTLCOLOR_BTN ||
		nCtlColor == CTLCOLOR_STATIC ) {
		pDC->SetBkColor( GetSysColor(COLOR_BTNFACE) );	
		//または、
		//pDC->SetBkMode( TRANSPARENT );	
		return hbrBack;
	} else {
		return CView::OnCtlColor( pDC, pWnd, nCtlColor );
	}
}
 メッセージの戻り値は,必ず背景を塗り潰すブラシのハンドルを返さなければなりません.このブラシはSetBkColor()で指定した色と同じ色のブラシにするのが普通です.
 どうしてわざわざ二通りの方法で二つも背景色を指定しなければいけないのかというと,文字そのものの背景と,コントロールのウィンドウの背景とがあるのですね.
 面倒だけど,とにかく同じにすればよいでしょう.どうしても面倒な場合は,SetBkMode()でTRANSPARENTを指定して透明にするのがよいでしょう.
 質問では指定する色はライトグレー(灰色)ということでしたが,Win95に対応していくためにも,背景の色はGetSysColor()で得られるプッシュボタンの表面の色に合わせましょう.
 こうしないとWin95で動かした場合,チェックボックスの影などが一致しない場合があり変になります(ところで,標準の色である白ですら変になってしまうのですが,これでいいのでしょうか??? > Microsoft殿).
 lParamの下位ワードはコントロールの種類を意味していて,ボタンやスタティックなどのコントロールの種類を判別することができます.
 ダイアログボックスの場合,CTLCOLOR_DLGという種類も送られてくるのでダイアログ自身の背景も変えることができます.  MFCの場合は標準で灰色になっているようですが,実は勝手にWM_CTLCOLORを処理されていたんですね(たぶん).
 Win32の場合,WM_CTLCOLORというメッセージは無くなっていて,かわりに種類別にWM_CTLCOLORBTNやWM_CTLCOLORSTATICといったメッセージに変わっています.
 たぶんハンドルが32ビットになったための回避策として分けたのでしょう.MFCを使っている場合は気にしなくてもよいでしょう.

CTL3D.DLLなどを使う

 WM_CTLCOLORを使う方法は古典的な方法です.今ふうな方法としては,CTL3D.DLLやCTL3DV2.DLLやCTL3D32.DLLといった再配布可能なDLLを使う方法があります.
 これらのDLLを使うことによって,ダイアログとそのコントロールの背景を灰色にするばかりでなく,名前のごとくチェックボックスやリストボックスなどのコントロールを自動的に3D化してくれます.
 しかし有効なのはダイアログの中だけで,自分でCreateWindow()して作成したコントロールは3D化できません.
 利用方法は,リスト8-2を見てください.このようにDLLをロードして,DLL内の関数を二つだけ呼んでおけば,後は自動的にすべて3D化してくれます(図8-2).

[図8-2] :

[リスト8-2] :

class MYCTL3D {
private:
	HANDLE	hCtl3dLib;
	BOOL (WINAPI* pfnRegister)( HINSTANCE );
	BOOL (WINAPI* pfnAutoSubclass)( HINSTANCE );
public:
	BOOL Enable3dControls( HINSTANCE );
	~MYCTL3D();
} myctl3d;

BOOL MYCTL3D::Enable3dControls( HINSTANCE hInstance ) {
	hCtl3dLib = NULL;
	#ifdef WIN32
		//Win32の場合
		hCtl3dLib = LoadLibrary( "CTL3D32.DLL" );
		if( hCtl3dLib == NULL ) return FALSE;
	#else
		//Win16の場合
		hCtl3dLib = LoadLibrary( "CTL3DV2.DLL" );
		if( hCtl3dLib < HINSTANCE_ERROR ) return FALSE;
	#endif
	(FARPROC&)pfnRegister =GetProcAddress( hCtl3dLib, (LPCSTR)"Ctl3dRegister" );
	(FARPROC&)pfnAutoSubclass = GetProcAddress( hCtl3dLib, (LPCSTR)"Ctl3dAutoSubclass" );
	if( !pfnRegister( hInstance ) ) return FALSE;
	return pfnAutoSubclass( hInstance );
}

MYCTL3D::~MYCTL3D() {
	if( hCtl3dLib ) FreeLibrary( hCtl3dLib );
}

int PASCAL WinMain( ・・・・ ) {

	MYCTL3D	myctl3d;
	myctl3d.Enable3dControls( hInstance );

			:
			:
}
 CTL3D.DLLとCTL3DV2.DLLはWin16用で,CTL3D32.DLLはWin32用です.WindowsNT3.5ではsystemディレクトリに入っています.また,通称MSDNと呼ばれるMicrosoft Developer NetworkのCD-ROMにも入っています.CTL3D.DLLとCTL3DV2.DLLの違いは,,,知りません(笑.知っている方は教えてください).
 VC2.0のMFCの場合,CWinAppにEnable3dControls()という関数があり,リスト8-2と同じことをやってくれます.VC2.0のMFCの場合は,これを一番最初に呼んでおくのがよいでしょう.実は,リスト8-2はMFCのソースをまねたものです(ばれた).

DS_3DLOOKやWS_EX_CLIENTEDGEスタイルを使う

 Win95では別の方法をとらなくてはいけません.CTL3D.DLLを使う方法ですら,もはや古典的になろうとしています.
 VC2.0でCWinAppのEnable3dControls()を使った場合,関数内でバージョンチェックが行われており,Wind95で実行した場合ダイアログは白くなってしまいます.
 リスト8-2を利用して強制的に呼んでもCTL3D32.DLL自体がバージョンチェックを行っており,エラーメッセージが出てしまいます.ただし「Win95上でインストールしたVC2.0によって作られたCTL3D32.DLL」を使えば正常に動くみたいです.
 なんだか不安定なのでCTL3D32.DLLを使うのはやめたほうがよいみたいですね.
 というわけで,Win95からダイアログボックスのスタイルにDS_3DLOOKというスタイルが追加されており,このスタイルをリソースに書いておくと,背景の色やリストボックスの窪みなど自動的に3D化してくれます.
 これさえやれば,バッチグーです.しかし,ダイアログエディタが対応してない限り.RCファイルにテキストエディタで,手で書かなくてはならないので少々不便ですが.
◎RCファイルに書く例
  STYLE DS_MODALFRAME | 省略 | WS_SYSMENU | DS_3DLOOK
    
//SDK持ってない場合は即値を書く(反則).
 STYLE DS_MODALFRAME | 省略 | WS_SYSMENU | 0x00000004
 質問にあったように,自分でCreateWindow()してコントロールを作成する場合,DS_3DLOOKは付けられないので,WM_CTLCOLORを使うしかないと思います.
 リストボックスの窪みも付かないです.これはリスト8-3のようにWS_EX_CLIENTEDGEという新しいスタイルを指定するとできます.
 WS_EX_CLIENTEDGEはコントロールだけでなく,オーバーラップウィンドウにも指定するべきです.これをやると図8-3のようになります.

[図8-3] :

[リスト8-3] :

hwnd = CreateWindowEx ( WS_EX_CLIENTEDGE, szAppName,
			"Title",
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT, CW_USEDEFAULT,
			CW_USEDEFAULT, CW_USEDEFAULT,
			NULL, NULL,
			hInstance, NULL);
 WS_EX_CLIENTEDGEを付けないと,サイズ変更枠が外側半分だけしか見えません.ちなみにMFCを使った場合,ビューウィンドウに自動的に付けてくれるようです.
 ということは,MFCを使っていない今までのアプリケーションは,すべてサイズ変更枠が変になっているのです.
 今までのアプリケーションはダイアログは白くて変だしウィンドウの枠も半分だけで変です.勝手にやってくれればよいのにと思うのですが,何だかいろいろ事情があるようです.

バージョンを4.0にする

 実は,DS_3DLOOKを手で書けというのはウソです.EXEのバージョンを「4.0」にしてやると,DS_3DLOOKを付けなくても,自動的にDS_3DLOOKを付けてくれるようになっています.
 バージョンを4.0にする方法は,Win32の場合,リンクのオプションで,
 link /SUBSYSTEM:windows,4.0 ・・・
と指定します.
 Win16の場合,Win95対応のSDKを入手して,新しいrc.exeで/40というオプションを指定します.
 rc /40 project.res project.exe

結論

 というわけで,結局Win95が出ればバージョンを4.0にし,自分で作成したコントロールには必要であればWS_EX_CLIENTEDGEを付けWM_CTLCOLORを処理し,MFCを使っていなければオーバーラップウィンドウにもWS_EX_CLIENTEDGEを付け,CTL3D32.DLLは使わないというのがよいでしょう.

Back to FAQ main page