Win32
ウィンドウクラスを構築する Part.1


HomeProgramming TipsWin32SDK Tips[SDK-004]

 ダウンロード : ウィンドウクラス構築サンプル(2005/05/12) 

C++ のクラス構築にある程度慣れてくると自分で CWnd のような「ウィンドウクラス」を作りたくなる。
※ なることにしてくれ

そこで初心者を脱皮する段階の人は様々な壁が立ちはだかるわけだ。
ここでは、C++ の機能と Win32API を組み合わせて、オリジナルのウィンドウクラスを構築してみよう。

プログラムは何でもそうだが、初期化、実行、後片付けという三つのプロセスにより構成される。
C++ のクラスは便利なもので、このうち、初期化はコンストラクタが、後片付けはデストラクタが自動的に担当する。
ウィンドウを作る上で初期化と後片付けとは何かを考えてみよう。
ここでは例として、CBaseWindow という名前のクラスを作ることにする。

■ 初期化

ウィンドウを構築するプロセスで必要な Win32API は以下の通りだ。


    RegisterClassEx(RegisterClass)によるウィンドウクラスの登録
    CreateWindowEx(CreateWindow)によるウィンドウの作成


RegisterClass の戻り値は念のため保存しておく方がよい。
後片付けで必要となるからだ。
すると初期化で必要な情報は最低でも以下の通りとなる。


    HINSTANCE
    クラス名
    ウィンドウスタイル


サイズが必要という人もいるかもしれないが、そんなものは後から指定できる。
キャプション文字列も同様に SetWindowText すればいつでも変更できる。
HINSTANCE は GetModuleHandle() でいつでも取得できるため気にしなくても良い。
クラス名はユニークなもので良ければ起動時間から適当にでっち上げればよい(笑)。
アイコンは最初に登録しておいた方が良いというのなら、初期化の段階で情報を渡せばよい。
クラスの登録でプロシージャ関数が必要なのでメンバ関数として用意してやる。
メニュー?知らんな、そんなものは(爆)。

すると、最小単位のコンストラクタは以下の通りとなる。

※ まだ問題は残っているがとりあえず書いてみる。
※ このまま入力しても無駄です。偉い人にはそれがわからんのです。


// -------------------------------------------------------------------
// 定数定義
// -------------------------------------------------------------------

#define STYLE_WINDOW    (WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_BORDER)
#define EX_STYLE_WINDOW (NULL)
#define DEFAULT_CLASS   "CBaseWindow"

// -------------------------------------------------------------------
// メンバ変数
// -------------------------------------------------------------------

private:

    HWND    m_hWnd;         // ウィンドウハンドル
    ATOM    m_Atom;         // ウィンドウクラス登録時のATOM
    TCHAR*  m_pszClassName; // クラス名

// -------------------------------------------------------------------
// メンバ関数定義
// -------------------------------------------------------------------

    // ウィンドウプロシージャ
    static LRESULT CALLBACK WndProc(
        HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam
    );

// -------------------------------------------------------------------
// コンストラクタ
// -------------------------------------------------------------------

CBaseWindow::CBaseWindow(
    TCHAR* strClass = NULL,             // クラス名
    DWORD dwStyle   = STYLE_WINDOW,     // ウィンドウスタイル
    DWORD dwExStyle = EX_STYLE_WINDOW,  // 拡張ウィンドウスタイル
    DWORD dwIcon    = NULL              // アイコン
)
: m_hWnd(NULL)
, m_Atom(NULL)
, m_pszClassName(NULL)
{
    WNDCLASSEX wc;

    // ウィンドウクラスの作成(起動時間を元にユニークな名称とする)
    if (!strClass) strClass = DEFAULT_CLASS;
    m_pszClassName = new TCHAR[strlen(strClass) + 8 + 1];
    if (!m_pszClassName) return;
    wsprintf(m_pszClassName, "%s%08X", strClass, timeGetTime());

    // クラスの登録
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.lpszClassName = m_pszClassName;                      // クラス名
    wc.lpfnWndProc   = (WNDPROC)WndProc;                    // ウィンドウプロシージャ
    wc.style         = CS_VREDRAW | CS_HREDRAW;             // 占有指定
    wc.hInstance     = (HINSTANCE)::GetModuleHandle(NULL);  // インスタンスハンドル
    wc.hIcon         = dwIcon
                     ? LoadIcon(wc.hInstance, MAKEINTRESOURCE(dwIcon))
                     : LoadIcon(NULL, IDI_APPLICATION);     // デフォルトアイコン
    wc.hIconSm       = wc.hIcon;                            // ** 手抜き
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);         // カーソル形状
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // 背景色
    wc.lpszMenuName  = NULL;                                // メニュー無し
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    if (m_Atom = RegisterClassEx(&wc)){

        // ウィンドウの作成
        m_hWnd = CreateWindowEx(
            dwExStyle,                                      // 拡張ウィンドウスタイル
            m_pszClassName,                                 // ウィンドウ名
            NULL,                                           // キャプション
            dwStyle,                                        // ウィンドウスタイル
            CW_USEDEFAULT, CW_USEDEFAULT,                   // 好きな位置に表示
            CW_USEDEFAULT, CW_USEDEFAULT,                   // サイズ
            NULL, NULL, wc.hInstance, NULL                  // インスタンス指定
        );
        if (!m_hWnd) return;
        ::ShowWindow(m_hWnd, SW_SHOWDEFAULT);
        ::UpdateWindow(m_hWnd);
    }
}


作成直後に表示されては困る場合は、コンストラクタにフラグを渡して
ShowWindow と UpdateWindow を実行しないようにすればよい。

■ 後片付け

プログラムの終了時に(普通は)自動的に呼び出されるのがデストラクタである。
ここにウィンドウを閉じるためのコードを記述する。
まず、クラスオブジェクトが削除されるときにウィンドウが残っていればそれを DestroyWindow を呼び出してやる。
閉じたかどうかの確認には何かしらメンバ変数の値を利用する。
ここでは、WM_DESTROY のタイミングでウィンドウハンドルを格納しているメンバ変数を NULL とすることで判断する。
ウィンドウが閉じたらクラスの開放をしてやる。それが UnregisterClass である。
クラスの登録削除には RegisterClassEx() の戻り値である ATOM を利用する。


// -------------------------------------------------------------------
// デストラクタ
// -------------------------------------------------------------------
CWindow::~CWindow()
{
    if (m_hWnd) DestroyWindow(m_hWnd);
    if (m_Atom)
        UnregisterClass(
            (LPCTSTR)m_Atom,
            (HINSTANCE)::GetModuleHandle(NULL)
        );
    if (m_pszClassName) delete[] m_pszClassName;
}



■ とりあえずコンパイル

あと、足りないプロシージャ関数を簡単に追加する。
プロシージャ関数はコールバック関数なので static で定義する。
テストなので必要最小限のメッセージを処理する。


// -------------------------------------------------------------------
// ウィンドウプロシージャ
// -------------------------------------------------------------------
LRESULT CALLBACK CBaseWindow::WndProc(
    HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam
)
{
    switch (msg){
        case WM_CLOSE:
            ::DestroyWindow(hWnd);
            m_hWnd = NULL;
            return 0;
        case WM_DESTROY:
            ::PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}


さて、ここまで出来たらコンパイルしてみる。ビルドエラーが出ているはずだ。
m_hWnd というメンバ変数にアクセスできない。
これは、静的なメンバからは通常メンバのアドレスが不明であるため、コンパイルが出来ないのだ。

以下、コレを解決するとってもナイスな方法をご紹介する。

> Part.2に進む 



 Copyright 2005 VALGUS. All Rights Reserved.