003:ウィンドウを作る

ウィンドウを作る

まずはWin32APIの基礎中の基礎、ウィンドウを作ってみます。
何もしないウィンドウを作ってみましょう。

[ファイル(F)]→[新規作成(N)]→[プロジェクト(P)]を選び、[新しいプロジェクト]ウィンドウを開きます。

プロジェクトの種類: スマートデバイス
テンプレート: Win32 スマートデバイス プロジェクト
プロジェクト名: win01

上記設定を行い[OK]ボタンを押します。

Win32 スマートデバイス プロジェクト ウィザード画面になります。
[次へ>]ボタンを押します。

プラットフォームの選択画面で、Windows Mobile 5.0 PocketPC SDKと、Windows Mobile 6 Professional SDKを選択します。
[次へ>]ボタンを押します。

プロジェクトの設定画面になります。
今回は、Windowsアプリケーションを選択しWindowの雛形を生成しましたが、今回は自分で一から作成してみたいと思います。追加のオプションで「空のプロジェクト」を選択します。

アプリケーションの種類: Windowsアプリケーション
追加のオプション: 空のプロジェクト

上記設定を行い、[完了]ボタンを押します。
空っぽのプロジェクトが作成できました。

ここにソースを追加していきます。
ソリューションエクスプローラの「ソースファイル」を右クリックし、メニューから[追加(D)]→[新しい項目(W)]を選択します。

カテゴリ: コード
テンプレート: C++ファイル(cpp)
ファイル名: win01

上記を選択・入力し[追加]ボタンを押します。

追加されたファイルに、以下の内容を入力します。
// win01.cpp

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

WCHAR szClassName[] = L"win01"; // ウィンドウクラス。UNICODEとしての文字列定数

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine,int nShowCmd)
{
 MSG msg;
 BOOL bRet;

 if (!InitApp(hInstance))
  return FALSE;
 if (!InitInstance(hInstance,nShowCmd))
  return FALSE;
 while*1 != 0) {
  if (bRet == -1){
   break;
  } else {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
 }
 return (int)msg.wParam;
}

// ウィンドウクラスの登録

ATOM InitApp(HINSTANCE hInst)
{
 WNDCLASSW wc;
 wc.style  = CS_HREDRAW|CS_VREDRAW;
 wc.lpfnWndProc = WndProc; // プロシージャ名
 wc.cbClsExtra = 0;
 wc.cbWndExtra = 0;
 wc.hInstance = hInst;
 wc.hIcon  = NULL;  // 未サポート
 wc.hCursor  = NULL;  // 未サポート
 wc.hbrBackground= (HBRUSH) COLOR_WINDOW;
 wc.lpszMenuName = NULL;  // 未サポート
 wc.lpszClassName=(LPCWSTR) szClassName;

 return (RegisterClassW(&wc));
}

// ウィンドウの生成
BOOL InitInstance(HINSTANCE hInst, int nShowCmd)
{
 HWND hWnd;

 hWnd = CreateWindowW(szClassName,L"Window Title",
  WS_CLIPCHILDREN, // ウィンドウの種類
  CW_USEDEFAULT,  // x座標
  CW_USEDEFAULT,  // y座標
  CW_USEDEFAULT,  // 幅
  CW_USEDEFAULT,  // 高さ
  NULL,    // 親ウィンドウのハンドル。親を作るのでNULL
  NULL,    // メニューハンドルまたは子ウィンドウID
  hInst,    // インスタンスハンドル
  NULL);
 if (!hWnd)
  return FALSE;
 ShowWindow(hWnd, nShowCmd);
 UpdateWindow(hWnd);
 return TRUE;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
 switch (msg){
  case WM_DESTROY:
   PostQuitMessage(0);
   break;
  default:
   return (DefWindowProc(hWnd, msg, wp, lp));
 }
 return 0;
}

それではF5キーを押してビルド&実行してみましょう。

ただの真っ白なウィンドウが作成されました。
画像を見てわかるとおり、下部のメニュー部分が白くなっています。
これはタスクバーの高さを考慮していないためです。
今後解決していきます。

プログラムの構造

ではプログラムを見ていきます。
プログラムは次のような構造をしています。

int WINAPI WinMain()
{
 1.ウィンドウクラスの登録
 2.ウィンドウの生成
 while(メッセージがあるか)
 {
  3.メッセージがあればウインドウプロシージャへ送出
 }
}

LRESULT CALLBACK WndProc()
{
 4.ウインドウに発生した色々な処理
 (マウスのクリックや、ウインドウサイズの変更など)
 に対する処理を記述する
}

WinMain関数がアプリケーションエントリーポイントになります。エントリーポイントとはプログラムの開始点のことです。WindowsアプリケーションはWinMain関数から始まると覚えてください。

WinMain関数の中ではウィンドウクラスの登録とウィンドウの生成を行います。
ウィンドウが生成されたら、メッセージループとなります。
Windowsでは、ウインドウに発生した色々な処理(マウスのクリックや、ウインドウサイズの変更など)がメッセージとしてやってきます。これらを適切に処理してやる必要があります。
これらの処理を記述するのがウィドウプロシージャです。プログラマがやりたいことをウィンドウプロシージャに記述します。

WinMain→ウィンドを作成→その後、メッセージをひたすら待ち続ける。

ウィンドウに発生する色々なイベント(メッセージ)→ウインドウプロシージャへ伝えられる。

ウィンドウクラス

ウィンドウを作るにはまず、ウィンドウクラスを登録する必要があります。
ウィンドウクラスを登録するにはWNDCLASSW構造体のメンバに各種設定を行います。
その後、RegisterClassW APIでウィンドウクラスを登録します。これはInitApp関数で行っています。

typedef struct {
    UINT style;   // ウインドウスタイル
    WNDPROC lpfnWndProc; // ウィンドウプロシージャ
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance; // インスタンスハンドル
    HICON hIcon;  // アイコン
    HCURSOR hCursor;  // カーソル
    HBRUSH hbrBackground; // 背景ブラシ
    LPCTSTR lpszMenuName; // メニュー名
    LPCTSTR lpszClassName; // クラス名
} WNDCLASS, *PWNDCLASS;

style ではクラスのスタイルを指定します。
CS_VERDRAW,CS_HREDRAW,CS_DBLCLKS,CS_PARENTDC,CS_NOCLOSEがサポートされます。
CS_VERDRAW:クライアント領域の幅が変更された場合に再描画を行う
CS_HREDRAW:クライアント領域の高さが変更された場合に再描画を行う
CS_DBLCLKS:ダブルクリックを検出する
CS_PARENTDC:子ウィンドウが親ウィンドウで描画されるように子ウィンドウのクリッピング領域を親ウィンドウのクリッピング領域に設定する。CS_PARENTDCスタイルを指定したウィンドウは子ウィンドウが親ウィンドウ上で描画できるようになります。
CS_NOCLOSE:ウィンドウ・メニューの[閉じる]を無効にします。

hIcon、hCursor、hbrBackgroundはWindows MobileではサポートされていませんのでNULLをセットします。

RegisterClassW APIを使用してウィンドウクラスを登録します。引数にはWNDCLASS構造体へのポインタを指定します。

ウィンドウ作成

ウインドウ作成にはCreateWindowW() APIを使用します。

HWND CreateWindow(
  LPCTSTR lpClassName,  // 登録されているクラス名
  LPCTSTR lpWindowName, // ウィンドウ名
  DWORD dwStyle,        // ウィンドウスタイル
  int x,                // ウィンドウの横方向の位置
  int y,                // ウィンドウの縦方向の位置
  int nWidth,           // ウィンドウの幅
  int nHeight,          // ウィンドウの高さ
  HWND hWndParent,      // 親ウィンドウまたはオーナーウィンドウのハンドル
  HMENU hMenu,          // メニューハンドルまたは子ウィンドウ ID
  HINSTANCE hInstance,  // アプリケーションインスタンスのハンドル
  LPVOID lpParam        // ウィンドウ作成データ
);

x、y、nWidth、nHeightにCW_USEDEFAULTを指定すると、全画面ウィンドウになります。

dwStyleには次のようなスタイルがあります。
WS_CLIPCHILDREN:親の描画で子ウインドウの部分をクリッピングします。親ウィンドウを作成する場合、このスタイルを使用します。
WS_BORDER:細い境界線のウィンドウを作成します。
WS_CAPTION:タイトルバーを持つウィンドウを作成します。
WS_CHILD:子ウィンドウ
WS_CLIPSIBLINGS:
特定の子ウィンドウがWM_PAINTメッセージを受け取る場合、WS_CLIPSIBLINGSスタイルは更新される子ウィンドウの領域からの他のすべてのオーバーラップする子供ウィンドウをクリップ(切り取り)ます。
WS_CLIPSIBLINGS が指定されていない場合に子ウィンドウがオーバーラップしていると子ウィンドウのクライアント領域内を描画するときに重なっている子ウィンドウのクライアント領域内にも描画されます

hWndParentには親ウィンドウのハンドルを指定しますが、親ウィンドウを作成する場合はNULLを指定します。
hInstanceにはインスタンスハンドルを指定します。
lpParamにはウィンドウ作成データを指定します。必要ない場合はNULLを指定します。

メッセージループ

Windowの生成が完了した後は、whileループになっています。
ウインドウに発生した色々な処理(マウスのクリックや、ウインドウサイズの変更など)は一旦メッセージキューという所にたまっています。それを取り出して、そのメッセージに合わせた処理を行う必要があります。メッセージを取り出すには、GetMessageを使用します。

BOOL GetMessage(
  LPMSG lpMsg,         // メッセージ情報
  HWND hWnd,           // ウィンドウのハンドル
  UINT wMsgFilterMin,  // 最初のメッセージ
  UINT wMsgFilterMax   // 最後のメッセージ
);

メッセージキューからメッセージを取得し、指定された構造体にそのメッセージを格納します。
GetMessageの戻り値は、WM_QUIT メッセージを取得した場合、0 が返ります。WM_QUIT メッセージはウィンドウを閉じることを要求するものです。
また、エラーが発生した場合には -1 が返ります。
0を受け取った場合はwhile文のメッセージループを抜けます。また-1の場合もbreakでメッセージループを抜けています。

取り出したメッセージは
DispatchMessageを使用して、メッセージをウィンドウプロシージャに渡します。

DispatchMessage関数
ウィンドウプロシージャへメッセージをディスパッチ(送出)します。

LRESULT DispatchMessage(
  CONST MSG *lpmsg   // メッセージ情報
);

■ウィンドウプロシージャ

メッセージが、DispatchMessage関数によりウィンドウプロシージャへ送られます。
switch文によって、メッセージの種類を判定し、メッセージにあった処理を行います。
このプログラムでは、WM_DESTROYを受け取っています。WM_DESTROYはウィンドウが破棄されるときに送られてきます。そしてPostQuitMessageによりシステムにプログラム終了リクエストを送ります。

それ以外のメッセージは、DefWindowProc関数に渡して処理してもらっています。

DefWindowProc関数は既定のウィンドウプロシージャを呼び出し、アプリケーションが処理しない任意のウィンドウメッセージに対する既定の処理を行ってくれます。

WM_DESTROYを受け取るまでひたすらメッセージを待ち続けます。

今日はここまで。

*1:bRet = GetMessage(&msg, NULL, 0, 0

上部へスクロール