Tips of VC++ > ウィンドウ > ウィンドウベースのアプリケーションを作る
前へ戻る次へ進む


このドキュメントにはサンプルプログラムが含まれています。
ワークスペース
ソースファイル(テキスト)


ウィンドウベースのアプリケーションを作る

ここで言うウィンドウベースのアプリケーションというのは MFCで言うところのSDIのことです。 厳密に言うと、ダイアログもウィンドウベースに含まれるのですが、 まあ、目を瞑ってください。(^^;

リソースが使えないので、全てをコーディングで行います。 って、そんなに難しくないですよ。 ウィンドウクラスを作って、メッセージループを作って、 プロシージャをがりがり書けば終わりです。

メインとなるAPIはCreateWindowというものです。 動的にコントロールを配置する場合にも使われるので よく覚えてください。

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        // ウィンドウ作成データ
);

lpClassNameのウィンドウクラスについては こちらを読んでください。 ウィンドウクラスというのは、そのウィンドウが使うプロシージャや スタイルなどが定義されたグループみたいなものです。 エディットボックスは"EDIT"、リストボックスは"LISTBOX"などのように 文字列で識別されます。

lpWindowNameは、ウィンドウのタイトルバーになる部分です。 コントロールだと、表示されるテキストだったりします。

dwStyleはウィンドウスタイルを示す定数値(WS_XXX)を 演算子"|"で組み合わせて指定します。 他にも、コントロール特有の定数(ES_XXX,LBS_XXX,etc...)もあります。

x,y,nWidth,nHeightは定番ですね。 hWndParentも分かると思います。メインウィンドウだったらNULLでいいのです。 hMenuも使わないならNULLでいいです。

hInstanceはWinMain関数のパラメータを使用してください。

lpParamはWM_CREATEメッセージが送られたときのlParamになります。 滅多に使いません。

戻り値がNULLかどうかはチェックしたほうがよいでしょう。

x,y,nWidth,nHeightにはCW_USEDEFAULTという定数を使用できます。 これを使う場合、座標や幅などはOSが勝手に決めてくれます。 私は使わないことをオススメしますが。

基本となるウィンドウスタイルは次のように分けられます。 表を作るまでもありませんが…。

サイズ変更可 WS_OVERLAPPEDWINDOW
サイズ変更不可 WS_OVERLAPPED|WS_SYSMENU

また、WS_OVERLAPPEDWINDOWを使いつつも 最大化ボタンを使えないようにしたいのであれば、 WS_OVERLAPPEDWINDOW&(~WS_MAXIMIZEBOX)というようにもできます。 いろいろ組み合わせてください。

この関数が成功すると、制御が戻る前に ウィンドウにWM_CREATEメッセージが送られます。 このとき、まだウィンドウが表示されていないことに注意してください。 WM_CREATEメッセージを処理するときによく間違えます。(^^;A

ウィンドウを表示する関数は、次の関数です。

BOOL ShowWindow(
  HWND hWnd,     // ウィンドウのハンドル
  int nCmdShow   // 表示状態
);

nCmdShowにSW_SHOWを渡すだけです。 SW_HIDEを渡すと表示されなくなるのがミソ。便利です。 これで表示されなくなっても、ウィンドウが破棄されたわけじゃないので 再び表示させるたときも、コントロールの状態などはそのままなのです。 ああ便利。って、けっこう定番ですけど。とにかく重宝します。

次にメッセージループを書きます。 これを書かないと、メッセージが受理できないばかりか、 ウィンドウが表示された瞬間、スレッドが終了してしまいます。
といっても、次のを丸写ししてください。

MSG msg;
while ( ::GetMessage(&msg,NULL,0,0) )
{
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
}

これが基本で、たまにちょっと追加することがあります。 まず、GetMessageで、キューにたまったメッセージを構造体に取得します。 続くTranslateMessageで、そのメッセージに付加する新たなメッセージを キューに入れ(WM_CHARとか)ます。 DispatchMessageさんの仕事は構造体のメッセージを プロシージャに送出することです。
キーボードアクセラレータを使うときは、以下のように変更します。

MSG msg;
HACCEL hAccel= ::LoadAccelerators(
    hAppModule,MAKEINTRESOURCE(IDR_ACCEL1));
while ( ::GetMessage(&msg,NULL,0,0) )
{
    if ( !::TranslateAccelerator(hWnd,hAccel,&msg) )
    {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
}

TranslateAccelerator関数は、キーボードからのメッセージを リソースのアクセラレータテーブルに従って WM_COMMANDなどのメッセージに変換して プロシージャの送出までしてくれる関数です。 したがって、これでメッセージが受理されたときは TranslateMessageやDispatchMessageなどは呼ばなくてもいいのです。

ウィンドウベースの基本はこの二通りぐらいです。 モードレスダイアログベースのときは、 IsDialogMessageとかいうのも出てきますが、それはまた別の話。

メッセージループはあまり複雑にしないほうがいいです。 スレッドの動作中は常にループしているわけですから、 ここの処理速度はすぐに反映されます。

次にプロシージャの作成となります。 ここがVCプログラミングのコアとなる部分ですね。 枠組みだけ書けばいいでしょう。これは丸写しで。

LRESULT CALLBACK WndProc(HWND in_hWnd,
                         UINT in_Message,
                         WPARAM in_wParam,
                         LPARAM in_lParam)
{
    switch ( in_Message )
    {
    case WM_DESTROY:
        ::PostQuitMessage(0);
        break;
    default:
        return ::DefWindowProc(in_hWnd,
            in_Message,in_wParam,in_lParam);
    }
    return 0;
}

WM_DESTROYはウィンドウの×ボタンを 押したときにも呼び出されるので、必須です。 デフォルトの処理も必須です。

これまでのをまとめると次のような感じになります。 プロシージャだけでなく、ウィンドウクラスの作成から メッセージループまでの部分は、ほとんどのアプリケーションを 作る際に共通なので、定型文として保存しておくのが便利です。 それをサンプルにもしておきます。

const char szClassName[]="アプリケーション名";

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow )
{
    // ウィンドウクラスの登録
    WNDCLASSEX wc;
    wc.cbClsExtra = 0;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.cbWndExtra = 0;
    wc.hbrBackground = (HBRUSH)::GetStockObject(BLACK_BRUSH);
    wc.hCursor = ::LoadCursor(NULL,MAKEINTRESOURCE(IDC_ARROW));
    wc.hIcon = NULL;
    wc.hIconSm = NULL;
    wc.hInstance = hInstance;
    wc.lpfnWndProc = (WNDPROC)WndProc;
    wc.lpszClassName = szClassName;
    wc.lpszMenuName = NULL;
    wc.style = CS_HREDRAW|CS_VREDRAW;
    ::RegisterClassEx(&wc);

    // ウィンドウの表示
    HWND hWnd= ::CreateWindow(szClassName,szClassName,
        WS_OVERLAPPEDWINDOW&~WS_MAXIMIZEBOX,
        100,100,400,300,
        NULL,NULL,hInstance,0);
    if ( hWnd==NULL )
        return 0;

    ::ShowWindow(hWnd,nCmdShow);

    // メッセージループ
    MSG msg;
    while ( ::GetMessage(&msg,NULL,0,0) )
    {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    return 0;
}

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

Oct.11, 2002