WisdomSoft - for your serial experiences.

ウィンドウ生成

コンテンツを何も表示しない空のウィンドウを表示します。ウィンドウの雛形となるウィンドウクラスを RegisterClass() 関数で登録し、CreateWindow() 関数でウィンドウの実体を生成します。

ウィンドウクラスの登録

Windows では GUI オブジェクトのことをコントロールと呼んでいます。多くの Windows アプリケーションで用いられているボタンやチェックボックス、ラジオボタン、リスト、コンボボックスなどは、独自に作られたものを除いて Windows が提供している部品を組み合わせたものです。これらの Windows コントロールは、プログラムの世界では全てがウィンドウとして扱われます。

Windows デスクトップアプリケーションは、何よりもベースとなるアプリケーションの主ウィンドウを作らなければ始まりません。新しくウィンドウを作成し、これを正常に動作させるには、いくつかの手続きを Windows との間に交わさなければなりません。最初は難しく感じるかもしれませんが、全体の概念や構造を理解してしまえば簡単です。

ウィンドウを生成するには、まずウィンドウクラスと呼ばれる情報を登録しなければなりません。ウィンドウクラスとは、ウィンドウの生成に必要な情報を持つ WNDCLASS 構造体のことで、ウィンドウのアイコンやマウスカーソル、背景色や名前などを設定します。これをシステムに登録することで、WNDCLASS 構造体で設定した情報に基づいたウィンドウを作ることができるようになります。WNDCLASS 構造体は次のように宣言されています。

WNDCLASS 構造体
typedef struct _WNDCLASS { 
    UINT    style; 
    WNDPROC lpfnWndProc; 
    int     cbClsExtra; 
    int     cbWndExtra; 
    HANDLE  hInstance; 
    HICON   hIcon; 
    HCURSOR hCursor; 
    HBRUSH  hbrBackground; 
    LPCTSTR lpszMenuName; 
    LPCTSTR lpszClassName; 
} WNDCLASS;

アプリケーションは、まずこの構造体の値を用意し、それぞれのメンバを正しく初期化しなければなりません。

style メンバにはウィンドウクラスの基本的な設定を行うビットフラグを指定します。ここに設定する値をウィンドウクラススタイル(Window Class Style)と呼びます。このメンバに指定するビットフラグは CS_ ではじまる定数として winuser.h で定義されています。一般には、ウィンドウのサイズが水平方向に変更されたときに再描画することを表す CS_HREDRAW と、垂直方向に変更されたときに再描画することを表す CS_VREDRAW を論理和で結合した値を代入しておきます。

表1 クラススタイル定数
定数 意味
CS_BYTEALIGNCLIENT ウィンドウのクライアント領域内を水平方向のバイト境界に揃える。
CS_BYTEALIGNWINDOW ウィンドウを水平方向のバイト境界に揃える。
CS_CLASSDC このクラスのウィンドウで共有するデバイスコンテキストを割り当てる。
CS_DBLCLKS ダブルクリックメッセージを受信する。
CS_DROPSHADOW ドロップシャドウ効果を有効にする。
CS_GLOBALCLASS すべてのアプリケーションで利用できるグローバルなクラス。
CS_HREDRAW 水平方向のサイズが変更されたときにウィンドウを再描画する。
CS_NOCLOSE システムメニューの「閉じる」コマンドを無効にする。
CS_OWNDC 個々のウィンドウに固有のデバイスコンテキストを割り当てる。
CS_PARENTDC 親ウィンドウのデバイスコンテキストを利用する。
CS_SAVEBITS ウィンドウが別のウィンドウで隠されたとき、隠された部分をビットマップでキャッシュする。
CS_VREDRAW 垂直方向のサイズが変更されたときにウィンドウを再描画する。

lpfnWndProc メンバは、ウィンドウの動作を処理する関数へのポインタを指定します。この関数は、ウィンドウに何らかのイベントが発生するたびに呼び出されるコールバック関数です。この関数を一般にウィンドウプロシージャと呼んでいます。ウィンドウプロシージャは、ウィンドウのあらゆる動作を適切に処理できなければなりませんが、それを全てプログラムしていては大変です。そこで、基本的な動作を処理してくれる DefWindowProc() 関数が存在するので、この場ではこれを利用します。

cbClsExtra メンバと cbWndExtra メンバは、この場では使わないので 0 を指定してください。これらのメンバはウィンドウクラスやウィンドウに対して、データを格納するための追加領域を指定します。

hInstance メンバには、このウィンドウクラスを登録するアプリケーションのインスタンスハンドルを指定します。WinMain() 関数が引数で受け取ったインスタンスハンドルを設定してください。

hIcon メンバにはウィンドウのアイコンを設定します。HICON はアイコンのハンドルを表す型です。アイコンについては後ほど詳しく解説します。NULL を指定した場合は標準のアイコンがそのまま表示されます。

hCursor メンバは、マウスカーソルがウィンドウのクライアント領域内に移動してきたときに表示するカーソルを指定します。HCURSOR はカーソルのハンドルを表す型です。カーソルの扱いも、アイコンとよく似ています。NULL を指定した場合は、現在のカーソルがそのまま表示されます。

hbrBackground メンバは、ウィンドウのクライアント領域をどのように塗りつぶすかを設定します。HBRUSH 型はブラシのハンドルと呼ばれます。ブラシとは、面をどのように塗りつぶすかを表した情報のことで、Windows では面の内部を塗りつぶすときブラシの情報を利用します。多くの場合はシステムに設定されている色を用いて塗りつぶすと考えられるため、COLOR_ ではじまるシステムカラーを示した定数を設定することもできます。ただし、このメンバにシステムカラーを表す定数を設定する場合は、値に1を加算してください。

lpszMenuName メンバは、このウィンドウのデフォルトメニューとなるメニューの名前を指定します。メニューについては後ほど詳しく解説します。今は使わないので、このメンバには NULL を指定します。

lpszClassName メンバには、この構造体に割り当てる一意なクラス名を指定します。この名前はこのクラスを識別するために用いられるもので、ウィンドウを作るときに、どのウィンドウクラスを使うか指示するときに利用されます。小さなプロジェクトであればプロジェクト名を指定すれば十分です。製品であれば、できる限り一意な名前になるように、製品公開を行う URI や、商号を使った名前規約を定めると良いでしょう。

WNDCLASS 構造体の初期化が終われば、これを Windows に登録する必要があります。ウィンドウクラスを設定するには RegisterClass() 関数に WNDCLASS 構造体へのポインタを渡します。

RegisterClass() 関数
ATOM RegisterClass(CONST WNDCLASS *lpwc);

lpwc パラメータに正しく初期化された新しいウィンドウクラスを表す WNDCLASS 構造体へのポインタを指定します。

この関数は、構造体に設定されている情報を元に、新しいウィンドウクラスを作成します。関数が成功すれば登録されたクラスを一意に示す整数値を返します。この値はアプリケーション固有のものではなく、システムがグローバルに管理している値でアトムと呼ばれます。アトムは ATOM 型で表現される整数値です。このアトムは、例えばウィンドウクラスの登録を削除する場合などに利用することができます。ただしウィンドウクラスの登録に失敗すると 0 を返します。

ウィンドウの生成と表示

ウィンドウクラスの登録が正しく行われれば、lpszClassName メンバに設定したウィンドウクラスの名前から、ウィンドウクラスの情報に基づいたウィンドウの実体を生成できます。新しいウィンドウを生成するには CreateWindow() 関数を使います。

CreateWindow() 関数
HWND CreateWindow(
  LPCTSTR lpClassName, LPCTSTR lpWindowName,
  DWORD dwStyle,
  int x, int y, int nWidth, int nHeight,
  HWND hWndParent, HMENU hMenu,
  HANDLE hInstance, LPVOID lpParam
);

lpClassName パラメータに、登録済みのウィンドウクラス名を指定します。ここには RegisterClass() で登録したウィンドウクラスの名前を指定します。lpClassName には文字列だけではなく、RegisterClass() 関数が返したアトムを指定することも可能です。アトムを LPCTSTR 型にキャスト変換して渡せば、指定したアトムが参照するウィンドウクラスが選択されます。

lpWindowName パラメータにはウィンドウのテキストを指定します。ウィンドウによっては、タイトルバーに表示したり、ボタンのラベルとして用いられたりします。このテキストが何に使われるかは、ウィンドウの種類によって異なります。

dwStyle パラメータにはウィンドウの種類や状態を表すビットフラグを組み合わせて指定します。dwStyle パラメータに設定できる定数は数多く定義され、生成するウィンドウの種類によっても使い分けなければなりません。基本的なスタイルを表す定数を表1にまとめます。

表2 ウィンドウスタイル定数
定数 意味
WS_BORDER 境界線を持つウィンドウを表す。
WS_CAPTION タイトルバーを持つウィンドウを表す。WS_BORDER スタイルも含む。
WS_CHILD
WS_CHILDWINDOW
子ウィンドウを表す。WS_POPUP スタイルと組み合わせることはできない。
WS_CLIPCHILDREN 親ウィンドウ内部を描画するとき、子ウィンドウが占める領域を除外することを表す。このスタイルは、親ウィンドウに対して指定する。
WS_CLIPSIBLINGS 描画時に兄弟関係にある子ウィンドウをクリップする。
WS_DISABLED ユーザーからの入力を受け付けない無効 (使用不能) なウィンドウを表す。
WS_DLGFRAME ダイアログボックスで一般的に使われるスタイル。境界を持つウィンドウを作成します。
WS_GROUP コントロールグループの最初のコントロールを表す。同じウィンドウ内で、次に WS_GROUP スタイルが設定されたコントロールまでが、同一のコントロールグループと解釈される。
WS_HSCROLL 水平スクロールバーを持つウィンドウを表す。
WS_MAXIMIZE 最大化ウィンドウを表す。
WS_MAXIMIZEBOX 最大化ボタンを持つウィンドウを表す。WS_SYSMENU スタイルと組み合わせる必要がある。WS_EX_CONTEXTHELP スタイルと組み合わせることはできない。
WS_MINIMIZE
WS_ICONIC
最小化ウィンドウを表す。
WS_MINIMIZEBOX 最小化ボタンを持つウィンドウを表す。WS_SYSMENU スタイルと組み合わせる必要がある。WS_EX_CONTEXTHELP スタイルと組み合わせることはできない。
WS_OVERLAPPED
WS_TILED
オーバーラップウィンドウを表す。
WS_OVERLAPPEDWINDOW
WS_TILEDWINDOW
WS_OVERLAPPED スタイル、WS_CAPTION スタイル、WS_SYSMENU スタイル、WS_THICKFRAME スタイルWS_MINIMIZEBOX スタイル、WS_MAXIMIZEBOX スタイルを組み合わせたオーバーラップウィンドウを表す。
WS_POPUP ポップアップウィンドウを表す。WS_CHILD スタイルと組み合わせることはできない。
WS_POPUPWINDOW WS_BORDER スタイル、WS_POPUP スタイル、WS_SYSMENU スタイルを組み合わせたポップアップウィンドウを表す。
WS_SYSMENU タイトルバー上にウィンドウメニューボックスを持つウィンドウを表す。WS_CAPTION スタイルも組み合わせる必要がある。
WS_TABSTOP 「Tab」キーで入力フォーカスを移動できるコントロールの一種であることを表す。
WS_THICKFRAME
WS_SIZEBOX
サイズ変更境界を持つウィンドウを表す。
WS_VISIBLE 可視状態のウィンドウを表す。
WS_VSCROLL 垂直スクロールバーを持つウィンドウを表す。

例えば、ウィンドウがトップレベルなのか他のウィンドウの内部に存在する子ウィンドウなのか、枠があるのかないのかなどの設定を行います。一般的なアプリケーションウィンドウであれば WS_OVERLAPPEDWINDOW を指定します。また、ウィンドウは既定で非表示の状態で生成されます。生成と同時に表示するには WS_VISIBLE をスタイルを設定します。

x パラメータ、y パラメータ、nWidth パラメータ、nHeight パラメータはウィンドウの初期座標、幅、高さを指定します。座標は、スクリーン全体の左上を原点として、右側に行くほど X 座標が増え、下に行くほど Y 座標が増えていきます。座標やサイズは既定でピクセル単位です。ピクセルとは、デバイスが表示できる最小の点(画素)のことです。1 ピクセルの大きさはデバイスによって異なります。例えば、フルHD解像度の環境であれば X 軸が 0 ~ 1920、Y 軸が 0 ~ 1080 の範囲となります。

Windows デスクトップ環境では、デスクトップの左上隅を原点とする画面全体の座標系をスクリーン座標と呼び、ウィンドウの作業領域の左上隅を原点とした座標系をクライアント座標と呼んでいます。ウィンドウの作業領域に図形を描画するときは、ディスプレイ全体の座標で考えるよりも、作業領域を原点としたほうが簡単です。そのため、使用する関数やアプリケーションの状態に応じて使い分けることになるのでしょう。トップレベルのウィンドウを生成するときは、スクリーン座標として指定します。ただし、一般的なツール系アプリケーションは表示されるウィンドウの座標やサイズを固定する必要はありません。システムやユーザーの設定に任せてよい場合は CW_USEDEFAULT 定数を指定します。

hWndParent パラメータは子ウィンドウを生成する場合に親ウィンドウを指定したり、ダイアログなどのサブウィンドウを生成するときに、オーナーウィンドウを設定したりするために用います。今回は主ウィンドウを作るので NULL を指定します。

hMenu パラメータはメニューを設定します。ここも、今は使わないので NULL を指定してください。

hInstance パラメータには、ウィンドウを生成するアプリケーションのインスタンスハンドルを指定します。この引数には WinMain() 関数から取得したインスタンスハンドルを渡します。

lpParam パラメータは、ウィンドウが生成されようとしたときに発生するイベントにデータを渡す手段として提供されています。特別なことがない限り、この引数にも NULL を渡します。

ウィンドウが正しく作られれば、HWND 型のウィンドウハンドルが返ります。この値は、個々のウィンドウを識別するために使われるもので、ウィンドウを操作する関数を呼び出すときに必要となります。CreateWindow() が失敗すると NULL が返ります。

コード1
#include <windows.h>
#define WINDOW_CLASS_NAME TEXT("WisdomSoft.Sample.Window")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;
	WNDCLASS wc;

	/*ウィンドウクラスの登録*/
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = DefWindowProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon =  NULL;
	wc.hCursor = NULL;
	wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
	wc.lpszMenuName = NULL;
	wc.lpszClassName = WINDOW_CLASS_NAME ;

	if (!RegisterClass(&wc))
	{
		MessageBox(NULL, TEXT("ウィンドウクラスの作成に失敗しました"), NULL , MB_OK);
		return 0;
	}

	/*登録したクラスのウィンドウを生成*/
	hWnd = CreateWindow(
		WINDOW_CLASS_NAME , TEXT("Window Title"),
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		100, 100, 400, 300, 
		NULL, NULL, hInstance ,NULL
	);

	if (hWnd) 
	{
		MessageBox(hWnd, TEXT("ウィンドウが生成されました"), TEXT("情報"), MB_OK);
	}
	else
	{
		MessageBox(NULL, TEXT("ウィンドウの生成に失敗しました"), NULL, MB_OK | MB_ICONERROR);
	}

	return 0;
}
実行結果
コード1 実行結果

コード1は、空のウィンドウを表示するだけの単純なプログラムです。プログラムの最後に MessageBox() 関数を呼び出していますが、これはプログラムが終了しないようにするためのストッパーです。これがなければ、ウィンドウを表示しても、すぐにプログラムが WinMain() 関数の末尾に到達して終了してしまいます。これではウィンドウが正しく表示できたのか確認できないので、プログラムを一時停止するためにメッセージボックスを表示しています。

CreateWindow() 関数に渡したウィンドウクラスの名前が間違っているなど、不正な引数が渡された場合は関数が失敗します。正しいウィンドウハンドルが得られなかった場合はエラーダイアログを表示します。

また、このプログラムではウィンドウの位置とサイズを固定していますが、ユーザーに合わせて自由にサイズを変更できるウィンドウであれば CreateWindow() 関数の x パラメータ、y パラメータ、nWidth パラメータ、nHeight パラメータに CW_USEDEFAULT 定数を指定するべきです。この場合、システムが適切な位置とサイズでウィンドウを作成します。

hWnd = CreateWindow(
	WINDOW_CLASS_NAME , TEXT("Window Title"),
	WS_OVERLAPPEDWINDOW | WS_VISIBLE,
	CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
	NULL, NULL, hInstance ,NULL
);

コード1の CreateWindow() 関数の位置とサイズを上記コードのように変更すれば、ウィンドウの初期位置とサイズがシステムによって自動的に設定されます。

拡張スタイル

RegisterClass() 関数と CreateWindow() 関数には、それぞれ受け取るパラメータ数を拡張した RegisterClassEx() 関数CreateWindowEx() 関数が存在します。ウィンドウの見た目や動作を設定するパラメータが加えられた点を除き、基本は RegisterClass() 関数と CreateWindow() 関数と同じです。

RegisterClassEx() 関数
ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx);

RegisterClassEx() 関数の lpwcx パラメータには、WNDCLASS 構造体を拡張したウィンドウクラスを表す WNDCLASSEX 構造体のポインタを指定します。

WNDCLASSEX 構造体
typedef struct {
    UINT cbSize;
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
    HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;

基本は WNDCLASS 構造体と同じですが cbSize メンバと hIconSm メンバが追加されています。

cbSize メンバには WNDCLASSEX 構造体のバイトサイズを設定します。通常 sizeof 演算子を用いて WNDCLASSEX 構造体のバイト数を取得し、その結果を代入します。

hIconSm メンバは、ウィンドウのタイトルバーで使われる小さなアイコンを表します。hIcon メンバに指定したアイコンと同じでよければ NULL を設定することもできます。

同様に CreateWindowEx() 関数もパラメータの大部分は CreateWindow() 関数と同じです。

CreateWindowEx() 関数
HWND CreateWindowEx(
  DWORD dwExStyle,
  LPCTSTR lpClassName,
  LPCTSTR lpWindowName,
  DWORD dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  HWND hWndParent,
  HMENU hMenu,
  HINSTANCE hInstance,
  LPVOID lpParam
);

CreateWindow() 関数のパラメータに加え、先頭の dwExStyle パラメータが追加されています。このパラメータには WS_EX_ から始まる拡張ウィンドウスタイルを表す定数を指定します。ウィンドウの表示について細かい設定が可能ですが、多くは左揃えを表す既定のスタイル WS_EX_LEFT 定数を設定すれば問題ないでしょう。

表3 拡張ウィンドウスタイル定数
定数 意味
WS_EX_ACCEPTFILES ドラッグアンドドロップで、ファイルを受け入れる。
WS_EX_APPWINDOW ウィンドウが可視化されたとき、トップレベルウィンドウがタスクバー上でフォーカスされる。
WS_EX_CLIENTEDGE 縁が沈んで見える境界線を持つウィンドウを表す。
WS_EX_CONTEXTHELP ダイアログボックスのタイトルバーに[?]ボタンを追加する。
WS_EX_CONTROLPARENT ユーザーが[Tab]キーを使って子ウィンドウ間を移動できることを表す。
WS_EX_DLGMODALFRAME 二重の境界線を持つウィンドウを表す。
WS_EX_LEFT 左揃えされたウィンドウを表す。既定の値。
WS_EX_LEFTSCROLLBAR 垂直スクロールバーがクライアント領域の左側に置かれる。
WS_EX_LTRREADING テキストが左から右に向かって読まれるよう表示する。既定の値。
WS_EX_MDICHILD MDI 子ウィンドウを表す。
WS_EX_NOPARENTNOTIFY このスタイルで作成された子ウィンドウが、作成されたり破棄されたりするとき、親ウィンドウに WM_PARENTNOTIFY メッセージを送らない。
WS_EX_OVERLAPPEDWINDOW WS_EX_CLIENTEDGE と WS_EX_WINDOWEDGE スタイルの組み合わせ。
WS_EX_PALETTEWINDOW WS_EX_WINDOWEDGE 、WS_EX_TOOLWINDOW、WS_EX_TOPMOST スタイルの組み合わせ。
WS_EX_RIGHT 右揃えされたウィンドウを表す。
WS_EX_RIGHTSCROLLBAR 垂直スクロールバーがクライアント領域の右側に置かれる。既定の値。
WS_EX_RTLREADING テキストが右から左に向かって読まれるよう表示する。
WS_EX_STATICEDGE ユーザーの入力を受け付けない項目を表すための、立体的に見える境界スタイルを持つウィンドウを表す。
WS_EX_TOOLWINDOW ツールウィンドウを表す。
WS_EX_TOPMOST 最前面ウィンドウを表す。
WS_EX_TRANSPARENT 透過ウィンドウを表す。
WS_EX_WINDOWEDGE ウィンドウが盛り上がった縁の境界線を持つことを表す。

RegisterClass() 関数と RegisterClassEx() 関数のどちらからでも、正しくウィンドウクラスが登録されていれば CreateWindow() 関数と CreateWindowEx() 関数のどちらからでもウィンドウを生成できます。

コード2
#include <windows.h>
#define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;
	WNDCLASSEX wcx;

	/*ウィンドウクラスの登録*/
	wcx.cbSize = sizeof(WNDCLASSEX);
	wcx.style = CS_HREDRAW | CS_VREDRAW;
	wcx.lpfnWndProc = DefWindowProc;
	wcx.cbClsExtra = 0;
	wcx.cbWndExtra = 0;
	wcx.hInstance = hInstance;
	wcx.hIcon =  NULL;
	wcx.hCursor = NULL;
	wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
	wcx.lpszMenuName = NULL;
	wcx.lpszClassName = WINDOWS_CLASS_NAME;
	wcx.hIconSm = NULL;

	if (!RegisterClassEx(&wcx))
	{
		MessageBox(NULL, TEXT("ウィンドウクラスの作成に失敗しました"), NULL , MB_OK);
		return 0;
	}

	/*登録したクラスのウィンドウを生成*/
	hWnd = CreateWindowEx(
		WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"),
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		100, 100, 400, 300,
		NULL, NULL, hInstance ,NULL
	);

	if (hWnd) 
	{
		MessageBox(hWnd, TEXT("ウィンドウが生成されました"), TEXT("情報"), MB_OK);
	}
	else
	{
		MessageBox(NULL, TEXT("ウィンドウの生成に失敗しました"), NULL, MB_OK | MB_ICONERROR);
	}

	return 0;
}

コード2コード1を改良して RegisterClassEx() 関数と CreateWindowEx() 関数を用いてウィンドウを表示します。拡張関数で追加されたパラメータには既定の値を設定しているため、実行結果はコード1と変わりません。