1.ウィンドウのクラス化


0.まずは前回のおさらいから

今回のウィンドウのクラス化に向けて前回のおさらいをしてみます。
まずウィンドウズアプリケーションにはWinMain()が必要で、 ウィンドウを作るにはウィンドウクラスの登録生成メッセージループとプロシージャによる管理が必要でした。
今回はその中のウィンドウに関する部分のクラス化について考えてみます。
ウィンドウクラスの登録と生成に関してはどのようにデータを設定するかについて幾つかの選択肢はありますが、
実装自体はそれほど困難ではないと思います。
残りはプロシージャですが、通常の生成方法で渡しているプロシージャ関数はグローバル関数なので
クラスのメンバ関数のポインタを直接は渡せないという問題があります。
そこで今回は私が専門学校時代の恩師に教えて頂いた方法を紹介しつつ、その単純な実装をしてみようと思います。

1.staticメンバ関数を登録

staticメンバ関数
class Test
{
public:
        static int Foo(){ return 0; }
};
このようにクラス内でstaticをつけて関数を作るとクラス名::関数名()という形でどこからでも呼べる関数が作れます。
型が同じであれば普通の関数ポインタにも格納できます
そうです。これを使えばクラスのメンバ関数をプロシージャ関数として登録することが出来るようになります。
ただこの方法にも問題があり、staticメンバ関数であるためにこの関数が呼ばれた段階では、
通知を受け取るべきインスタンスに関する情報がまったくないのです。
そこでプロシージャ関数の引数のウィンドウハンドルを元に
インスタンスを検索出来るように事前に準備をすることにします
まずインスタンスのメンバにウィンドウハンドルを用意し、
次に生成済みのインスタンスをリスト化する為にstaticメンバにコンテナを用意します。
(今回はstd::listを使用しました。)
そしてインスタンスが生成された段階でそれへのポインタをコンテナに格納し、
それが破棄された段階でそれへのアクセスを避ける為、コンテナから削除します。
このように事前に準備をすることでstaticメンバ関数が呼ばれた後にインスタンスを検索することが出来るようなりました。
後は個別のインスタンス用のメンバ関数版のプロシージャ関数を作ればちゃんと通知を受け取ることが出来ます。

2.実装

前章の考えをプログラム化すると下のような感じになりました。
コンストラクタ、デストラクタについては読み易くなるようにヘッダに書いておきました。
その他の関数については前回WinMain()の中でやっていた事が役割ごとに関数にまとまっているだけです。
抽象ウィンドウクラスの宣言
class AWindow
{
public:
    //コンストラクタでスタイル等の初期化とコンテナへの登録を行う
    AWindow() : hWnd_(0), style_(WS_OVERLAPPEDWINDOW), styleEx_(0)
    {
        strcpy_s(registerName_, REGISTER_NAME_MAX_LENGTH, "DEFAULT");
        windowList_.push_back(this);
    }
    //デストラクタでコンテナから削除
    virtual ~AWindow()
    {
        windowList_.remove(this);
    }
private:
    //!最初に呼ばれるグローバルなプロシージャ関数
    static LRESULT CALLBACK GlobalProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
private:
    //!派生クラスのオーバーライド用プロシージャ関数
    virtual LRESULT CALLBACK MyProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) = 0;
public:
    //!ウィンドウクラスの登録関数
    bool Register(HINSTANCE hInst);
    //!ウィンドウの生成関数 Register()が成功していることが前提
    bool Create(HINSTANCE hInst);
public:
    //!ウィンドウを可視化する関数 生成後に呼ぶこと
    void Open()
    {
        ShowWindow(hWnd_, SW_SHOW);
    }
    //!ウィンドウのタイトルバーの表示文字列設定関数 生成後に呼ぶこと
    void SetTitle(const char* title)
    {
        SetWindowText(hWnd_, (LPCSTR)title);
    }
    //!ウィンドウのスクリーンサイズ(クライアント領域の大きさ)設定関数 生成後に呼ぶこと
    void SetScreenSize(int w, int h);
    //!ウィンドウの位置を画面中心に移動させる関数 生成後に呼ぶこと
    void MoveDisplayCenter();
public:
    HWND GetHandle(){ return hWnd_; }
    int GetStyle(){ return style_; }
    int GetStyleEx(){ return styleEx_; }
    LPCSTR GetRegisterName(){ return registerName_; }

    void SetStyle(int style){ style_ = style; }
    void SetStyleEx(int styleEx){ styleEx_ = styleEx; }
    void SetRegisterName(LPCSTR name){ strcpy_s(registerName_, REGISTER_NAME_MAX_LENGTH, name); }
private:
    //!登録名の最大長
    enum{REGISTER_NAME_MAX_LENGTH = 32};
private:
    HWND hWnd_;                                     //!<ウィンドウハンドル
    int style_;                                     //!<スタイル
    int styleEx_;                                   //!<拡張スタイル
    CHAR registerName_[REGISTER_NAME_MAX_LENGTH];   //!<登録名
private:
    static std::list<AWindow*> windowList_;     //!<生成済みウィンドウのコンテナ
};
で上のクラスを使ったWinMain.cppは下のようになりました。
ウィンドウクラスを使用したWinMain.cpp
//-------------------------------------------------------------------
#include <windows.h>                        //インクルードファイル
#include "MMMAWindow.h"

//-------------------------------------------------------------------
#define WINDOW_TITLE    "テストウィンドウ"  //!<タイトル
#define WINDOW_WIDTH    640                 //!<横幅
#define WINDOW_HEIGHT   480                 //!<縦幅

//-------------------------------------------------------------------
//!メインウィンドウクラス
//-------------------------------------------------------------------
class MainWindow : public mmm::AWindow
{
public:
    MainWindow() : active_(false)
    {
        //TODO:ここでスタイルなどを設定
        SetStyle(WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
        SetStyleEx(0);
        SetRegisterName("TEST");
    }
    ~MainWindow(){}
private:
    virtual LRESULT CALLBACK MyProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        //TODO:ここでこのクラス用のプロシージャを定義
        switch(msg)
        {
        case WM_ACTIVATEAPP: active_ = (bool)(wParam != 0); break;
        case WM_DESTROY: PostQuitMessage(0); break;
        default: return DefWindowProc(hWnd, msg, wParam, lParam); break;
        };

        return  FALSE;
    }
public:
    bool IsActive(){ return active_; }
private:
    bool active_;   //!<このウィンドウがアクティブかどうか
};

//-------------------------------------------------------------------
static HINSTANCE g_hInstance = 0;       //!<インスタンスハンドル

//-------------------------------------------------------------------
//!Windows Programmingにおいて一番最初に呼ばれる関数
//-------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
    //インスタンスハンドルのコピー
    g_hInstance = hInst;

    //メインウィンドウ生成
    MainWindow mw;
    if(mmm::CreateWindowUTL(&mw, hInst, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE) == false) return 0;

    //メッセージループ
    MSG msg;
    while(1)
    {
        if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            //終了メッセージがきたら終了
            if(msg.message == WM_QUIT) break;

            DispatchMessage(&msg);
        }
        else
        {
            if(mw.IsActive())
            {
            }
            else
            {
                WaitMessage();
            }
        }
    }

    //プログラムの終了
    return msg.wParam;
}
前回のソースを見た人は感じると思いますが非常にすっきりしました。
まずソースの上のほうでAWindowを継承してMainWindowを定義しています。
コンストラクタでスタイルや登録名の設定を行い、MyProc()で各メッセージへの対応を定義するだけの単純な実装になっています。
そしてWinMain()の中でMainWindowを生成し、後はメッセージループを回すだけです。
MainWindowを生成する為に使っているmmm::CreateWindowUTL()というのはウィンドウクラスの登録、生成、大きさ、位置設定などの面倒な処理を流れに沿って行う為の便利関数です。

3.その他

とウィンドウのクラス化を見てきましたが、ソースを見てもらうと分かると思いますが、
今回はあくまで考え方がメインということで、非常にシンプルな作りになっています。
次回は多分アプリケーションクラスを作ると思います。
今回のソース。
戻る。