Mesoscopic Programming

タコさんプログラミング専門

ウィンドウズプログラミング講座第3回:ウィンドウクラスの作成

概要

今回は、ヌメロン講座で既出ですが改めてウィンドウクラスについてご説明いたします。

本当はクラス化の前にメニューやアクセラレータをやろうかと思ったんですが、そもそもメニューやキーアクセラレータのような基本的なことは入門書や他のサイトさんで腐るほど解説されているので、今更私のような新参者の出る幕ではないと思います。この講座を始めようと思ったきっかけは、私がググっても見つけられなかった情報をのっけたいという欲求が発端なのでなるべく基本的な部分はすっ飛ばしたいと思います。


class Window(ウィンドウ基底クラス)

「基底クラス」と呼ぶか「基本クラス」と呼ぶかで悩みましたが、改めてストラウストラップさんの C++*1見たら「基底クラス」と書いてあったのでここでは「基底クラス」と呼ぶことにします。

class Window
{
public :

    static LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
    static VOID    SetObject( HWND hWnd, Window * object );
    static Window  * GetObject( HWND hWnd );

    BOOL    override;
    HWND    hwnd;
    UINT    umsg;
    WPARAM  wparam;
    LPARAM  lparam;
    LRESULT lresult;

    virtual VOID MessageProc();

    virtual inline VOID OnCreate()     { override = FALSE; }
    virtual inline VOID OnDestroy()    { override = FALSE; }

    ~中略~

    virtual inline VOID OnPenWinLast() { override = FALSE; }
};
解説

前にも説明した通りこのウィンドウ基底クラスの役割は、今後のウィンドウの実装をなるたけ手抜きできるようにするためのものです。したがって省けるところはできるだけ省くという趣旨で設計されております。

  1. すべてのウィンドウメッセージを仮想関数として実装し派生クラスでは必要なメッセージ関数のみオーバーライドすればよい
  2. 決まりきった引数はメンバ変数としいちいちタイピングする必要なし
  3. 当然ながらコールバック関数も必要なし
  4. MFCのような既成品と違って気に入らなければいつでも改造可能

以上のような目標に向かって日夜努力した結果です。
MFCの何が嫌いって知らないところで何が行われているのか分からないし、せっかく C++ のようなエレガントな言語を使ってるのにブラックボックスで覆い隠そうとする悪の組織マイクロソフトの野望たるやまさにゲスの極み。


LRESULT CALLBACK Window :: WindowProc()

ウィンドウクラスの登録で使用するウィンドウメッセージの入口用コールバック関数です。

LRESULT CALLBACK Window :: WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    Window * object;

    if ( uMsg == WM_NCCREATE && lParam != NULL )
    {
        object = ( Window * ) ( ( LPCREATESTRUCT ) lParam )->lpCreateParams;

        SetObject( hWnd, object );
    }
    else
    {
        object = GetObject( hWnd );
    }

    if ( object != NULL )
    {
        object->override = TRUE;
        object->hwnd     = hWnd;
        object->umsg     = uMsg;
        object->wparam   = wParam;
        object->lparam   = lParam;
        object->lresult  = 0;

        object->MessageProc();

        if ( object->override )
        {
            return object->lresult;
        }
    }

    return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
解説

通常ならここで大量の switch ~ case 文で埋もれるわけですが、ウィンドウオブジェクトのアドレスをウィンドウ固有データに保存して、取得したオブジェクトアドレスから動的に処理してるのでシンプルです。
ちなみに前も言いましたが最初のメッセージは WM_CREATE ではなく WM_NCCREATE です、たぶん。

以上でウィンドウ基底クラスの説明はおしまいです。


class App(アプリケーションクラス)

メインウィンドウとなる派生クラスです。

class App : public Window
{
public :

    static const LPCTSTR appName;
    static const LPCTSTR appClass;

    BOOL    Init( HINSTANCE hInstance, int nCmdShow );
    VOID    Run();
    VOID    OnDestroy();
};

BOOL App :: Init( HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASSEX  wcx;
    DWORD       dwStyle;
    HWND        hWnd;

    ~中略~

    RegisterClassEx( & wcx );

    dwStyle = WS_OVERLAPPEDWINDOW;
    hWnd    = CreateWindow( appClass, appName, dwStyle,
                            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                            NULL, NULL, hInstance, this );

    ShowWindow( hWnd, nCmdShow );
    UpdateWindow( hWnd );

    return TRUE;
}

VOID App :: Run()
{
    MSG msg;

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

VOID App :: OnDestroy()
{
    PostQuitMessage( 0 );
}
解説

ごらんの通り、初期化してメッセージループして終了メッセージに対応するというパタンのやつです。


最後に

今後本講座で使用するすべての自作ウィンドウは今回のようなウィンドウ派生クラスを使用しますので、上記のようなメッセージ対応処理のパタンを覚えてください。
まあパタンと言ってもスイッチ-ケース文をメンバ関数に置き換えただけですけど。
本日は体調不良のためこのへんでお開きにしたいと思います。

ソースファイル
  1. main.h
  2. main.cpp
  3. Window.h
  4. Window.cpp