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 よりもいいかもね」