#pragma twice

KAB-studio > プログラミング > #pragma twice > 392 Version 18.05 ウィンドウプロシージャをメンバ関数にする!

#pragma twice 392 Version 18.05 ウィンドウプロシージャをメンバ関数にする!

前のページへ 表紙・目次へ 次のページへ

 Version 18.05
ウィンドウプロシージャをメンバ関数にする!

前回の最後で説明したように、ウィンドウプロシージャはただの関数で
あまり増やすことができません
そうすると変数が増えたときに大変ってことね
なので、ウィンドウプロシージャを普通のメンバ関数にします。そうすれ
ば変数を全部メンバ変数にできるから、データの管理やイベント処理が簡単
になります
おお!
というわけで、まずはそのプログラムから

// Main.cpp
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
#include "Dialog.h"

// WinMain() 。
int WINAPI WinMain
    ( HINSTANCE p_hInstance
    , HINSTANCE p_hPrevInstance
    , LPSTR p_pchCmdLine
    , int p_iCmdShow
    )
{
    // CDialog クラスでダイアログを作成します。
    CDialog cDialog;
    cDialog.DoModal( p_hInstance, IDD_MAIN );

    return 0;
}


// Dialog.h

// CDialog クラス。
class CDialog
{
private:
    // 最初にセットするための、自クラスのポインタ。
    static CDialog *m_pcDialog;

public:
    // ダイアログを作成します。
    int DoModal( HINSTANCE p_hInstance, int p_iDialogId );

    // ダイアログプロシージャ(形式上)。
    static BOOL CALLBACK DispatchDialogProc
    ( HWND p_hDlgWnd
    , UINT p_uiMessage
    , WPARAM p_wParam
    , LPARAM p_lParam
    );

    // ダイアログプロシージャ(実質)。
    BOOL DialogProc
    ( HWND p_hDlgWnd
    , UINT p_uiMessage
    , WPARAM p_wParam
    , LPARAM p_lParam
    );
};


// Dialog.cpp
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
#include "Dialog.h"

// 最初にセットするための、自クラスのポインタ。
CDialog *CDialog::m_pcDialog = NULL;

// ダイアログを作成します。
int CDialog::DoModal( HINSTANCE p_hInstance, int p_iDialogId )
{
    // this ポインタをグローバル変数に取っておきます。
    // あとでダイアログプロシージャで渡します。
    m_pcDialog = this;

    // ダイアログを作成します。
    int iRet
        = DialogBox
            ( p_hInstance
            , MAKEINTRESOURCE( p_iDialogId )
            , NULL
            , &DispatchDialogProc
            );

    return iRet;
}

// ダイアログプロシージャ(形式上)。
BOOL CALLBACK CDialog::DispatchDialogProc
    ( HWND p_hDlgWnd
    , UINT p_uiMessage
    , WPARAM p_wParam
    , LPARAM p_lParam
    )
{
    if( p_uiMessage == WM_INITDIALOG )
    {
        // 直前に DoModal() が呼ばれてる場合。
        // this ポインタをダイアログのユーザー領域に入れます。
        SetWindowLong( p_hDlgWnd, GWL_USERDATA, (LONG)m_pcDialog );
        m_pcDialog = NULL;
    }

    // ダイアログの 32 ビット整数に格納されている 
    // this ポインタを取りだします。
    CDialog *pcDialog 
        = (CDialog *)GetWindowLong( p_hDlgWnd, GWL_USERDATA );
    if( pcDialog == NULL )
    {
        // NULL の時は何もしません。
        return 0;
    }

    // メンバ関数のダイアログプロシージャを呼び出します。
    return 
        pcDialog->DialogProc
            ( p_hDlgWnd
            , p_uiMessage
            , p_wParam
            , p_lParam 
            );
}

// ダイアログプロシージャ(実質)。
BOOL CDialog::DialogProc
    ( HWND p_hDlgWnd
    , UINT p_uiMessage
    , WPARAM p_wParam
    , LPARAM p_lParam
    )
{
    if( p_uiMessage == WM_COMMAND )
    {
// ここは Version 18.03 ( No.390 ) の CDialog::DialogProc() と
// 同じです。

// Version 18.04 ( No.391 ) で SetWindowLong() 関数を呼び出す箇所を
// 追加している場合は、削除してください。削除しないと動きません!

            return TRUE;
        }
    }
    return FALSE;
}

長!
ちょっと構造が複雑だから分かりづらいかも。概略を説明するとこうなり
ます

WinMain()
 ↓
CDialog::DoModal()            ダイアログ作成。
 ↓
CDialog::DispatchDialogProc() 初回は SetWindowLong() でポインタセット。
 ↓
CDialog::DispatchDialogProc() GetWindowLong() でポインタ取得。
 ↓
DialogProc()                  ポインタを通して呼び出し。

ひとつひとつ見ていきます。まず WinMain() 関数では、これまでは
DialogBox() 関数を呼び出していたけど、この呼び出しを
CDialog クラスの DoModal() メンバ関数で呼び出すようにしています
これはなんで?
 CDialog クラスの static メンバ変数を使ったりするからね。
CDialog クラスには、自クラスのポインタを持つ static メンバ変数があり
ます

// 最初にセットするための、自クラスのポインタ。
CDialog *CDialog::m_pcDialog = NULL;

このポインタに、ダイアログ作成直前に this ポインタをセットします

// ダイアログを作成します。
int CDialog::DoModal( HINSTANCE p_hInstance, int p_iDialogId )
{
    // this ポインタをグローバル変数に取っておきます。
    // あとでダイアログプロシージャで渡します。
    m_pcDialog = this;

こうして、 static メンバ変数にとっておいてから DialogBox() 関数で
ダイアログを作成します。このとき、ダイアログプロシージャには
DispatchDialogProc() static メンバ関数を指定します
って言っても前回だって static メンバ関数だったし、これは名前が変
わっただけだよね
そうなんだけど、中身は全く違います。まず、ダイアログプロシージャ
が最初に呼ばれたとき、つまり最初のメッセージが送られてきたときに
ウィンドウの 32 ビット領域に、さっき取っておいた自クラスのアドレス
を保存します

    if( p_uiMessage == WM_INITDIALOG )
    {
        // 直前に DoModal() が呼ばれてる場合。
        // this ポインタをダイアログのユーザー領域に入れます。
        SetWindowLong( p_hDlgWnd, GWL_USERDATA, (LONG)m_pcDialog );
        m_pcDialog = NULL;
    }

ダイアログの場合、一番最初に送られるメッセージは WM_INITDIALOG 
なので、このときに Version 18.04 ( No.391 ) の方法でアドレスを
セットします
こうすると……
 WinMain() 関数で作った cDialog 変数のアドレスが、今作った
ダイアログの 32 ビット整数値にセットされるわけです

    CDialog cDialog; ← この変数のアドレス。

で、これが初回のみ。そのあとは、 GetWindowLong() という API で、
今セットしたアドレスをウィンドウから取得します。

    // ダイアログの 32 ビット整数に格納されている 
    // this ポインタを取りだします。
    CDialog *pcDialog 
        = (CDialog *)GetWindowLong( p_hDlgWnd, GWL_USERDATA );

なるほど、今度は逆に取り出して、それを CDialog クラスのポインタに
キャストするわけね
で、これを使ってメンバ関数を呼び出します

    // メンバ関数のダイアログプロシージャを呼び出します。
    return 
        pcDialog->DialogProc
            ( p_hDlgWnd
            , p_uiMessage
            , p_wParam
            , p_lParam 
            );

この DialogProc() が実質的なダイアログプロシージャになります
この中でメッセージの処理をして、しかも普通のメンバ関数である、と
そういうこと! この仕組みを使うことで、ウィンドウと cDialog 変数
を結びつけることができて、その DialogProc() メンバ関数を呼び出すこと
ができるというわけです

/*
    Preview Next Story!
*/
すっげー複雑!
確かに複雑かもね
色々な機能が組み合わさっているっていうか
でももうちょっと複雑なところまで説明するから
げげ
というわけで次回
< Version 18.06 イベントハンドラをオーバーライドで >
につづく!
 MFC を真似てるから、このくらいはね
ってことは MFC くらいすごいのができる?
すごいかどうかは分からないけど、 MFC よりもいいかもね
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。