Version 18.04
ウィンドウの 32 ビット整数値
「前回は、ウィンドウプロシージャを static メンバ関数にする方法を説明
しました」
『でもあんま意味ないよね』
「そう、はっきり言って意味ないです」
『うぉ言い切りやがった』
「なぜかというと、 static メンバ関数だから」
『 static メンバ関数だと普通の関数とほとんど同じだもんね』
「それに仮想関数にできないからオーバーライドもできない」
『……つまり、普通のメンバ関数でこれをやりたい、ってことよね』
「そういうこと! メンバ関数をウィンドウプロシージャにできれば、そこ
からメンバ変数を使ったりメンバ関数を呼び出すことができるし、
オーバーライドもできるから」
『ポリモーフィズムできる!』
「ってこと。ここからはその方法を説明していきます。さてそのために、
ウィンドウが持っている【 32 ビット整数値】というものを使います」
『…………?』
「ひとつひとつ見ていこうか。まず【 32 ビット整数値】は?」
『 32 ビットサイズの整数値、ってそれってつまり int 型と同じってこと
よね』
「そう、 int や long 、 WPARAM や LPARAM と同じ。このあたりは
Version 5.27 ( No.092 ) や Version 8.17 ( No.159 ) を読み返してみて
ください」
『つまり結局は整数値、ってことね。それをウィンドウが持ってる……?』
「ウィンドウって、元々いろんな情報を持っているよね。たとえばタイトル
とか、ウィンドウスタイルとか」
『あとウィンドウクラス名もウィンドウの情報だし、サイズとか位置とかも
ウィンドウの情報かも。あ、そういうののひとつに、その整数値があるって
こと?』
「そういうこと。今あげた、ウィンドウタイトルとかスタイルとかは、
ウィンドウにとって必要不可欠な情報だけど、そういう情報と一緒に、
好きな整数値をウィンドウに取っておくことができるんです」
『へー、そんなことできるんだ』
「これはプログラムで自由に使うことができます。ただし1つのウィンドウ
につきひとつだけだけどね」
『んー、それは少ないかも』
「でも実は、ひとつあれば十分なんです」
『へ、なんで?』
「たとえば WPARAM や LPARAM 。メッセージって、一緒にこの2つしか送れ
ないけど、それで大丈夫なのはなんでだっけ」
『えっと、確かポインタで……あ』
「そう、 Version 8.18 ( No.160 ) で説明したように、 LPARAM に構造体
やクラスのアドレスを渡しちゃえば」
『どんなに大きな情報も渡せちゃう……それと同じ事ができるんだ!』
「そういうこと。ウィンドウに入れられる整数値は 32 ビット、つまり
アドレスやポインタと同じだから、そこにクラスのアドレスを入れることが
できるんです」
『おお!』
「この仕組みを使って、ウィンドウに送られてきたメッセージを、クラスの
それぞれの変数が持つメンバ関数に送るようにします」
『そんなことできるんだ』
「 MFC はしてるしね」
『そーいえば』
「と、そこまで行く前に、まずこの、ウィンドウが持ってる 32 ビット
整数値っていうのを見ておこうか。まずプログラムを以下のように修正して
ください」
// Dialog.cpp
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
#include "Dialog.h"
// ダイアログプロシージャ。
BOOL CALLBACK CDialog::DialogProc
( HWND p_hDlgWnd
, UINT p_uiMessage
, WPARAM p_wParam
, LPARAM p_lParam
)
{
// (略)
// それを IDC_E_ANSWER にセットします。
SetWindowText( hAnswerWnd, pchAnswer );
// ここから追加。
// ウィンドウの 32 ビット整数値に計算結果をセットします。
SetWindowLong( p_hDlgWnd, GWL_USERDATA, iLeft + iRight );
// ここまで追加。
return TRUE;
}
}
return FALSE;
}
「つまり IDC_B_EQUAL ボタンを押したときの処理に SetWindowLong() 関数
の呼び出しを追加してください」
『これだけでいいんだ。 SetWindowLong() っていうのが、ウィンドウに
整数値をセットする…… API ?』
「一応それで合ってるけど、 SetWindowLong() の機能はそれだけじゃない
んです」
セットするウィンドウのウィンドウハンドル セットする値
↓ ↓
SetWindowLong( p_hDlgWnd, GWL_USERDATA, iLeft + iRight );
↑
セットする値の種類
「この SetWindowLong() 関数は、第2引数で指定した値をセットします。
GWL_USERDATA を指定すると、さっきから言ってる 32 ビット整数値になり
ます」
『他には?』
「たとえばウィンドウスタイル。 Version 5.24 ( No.089 ) で説明した
ウィンドウスタイルを実行中に変更する時は、この API に GWL_STYLE を
渡すことで変更できます」
『へー、こんな API で変更できるんだ』
「それと、 GetWindowLong() っていう API で、逆にそれぞれの値を取得
できるから。このふたつは、特に MFC を使わない場合は結構使うこと多い
と思うよ」
『 MFC 使わないとこういうののお世話になるってことね……』
「で、話を戻すと、この SetWindowLong() 関数を呼び出して、第2引数に
GWL_USERDATA を渡すことで、第3引数に渡した値が第1引数で指定した
ウィンドウ、ここではダイアログにセットされるわけです」
『へー。……で、これってそれでどうするの? 確認できないじゃん』
「実はできるんです。ビルドして実行までして」
『ビルドして実行、ほいと』
「次に Spy++ を起動して」
『お、久しぶりね』
「使い方を忘れてたら Version 3.4 ( No.029 ) を参考にしてください。
で、 Spy++ を起動したら、 Ctrl + F を押して」
『ダイアログ出てきた』
「中央の照準みたいなのを、さっき起動した NewCalc アプリのダイアログ
に重ねて」
『ほい、このダイアログのウィンドウハンドルとか表示されたよ』
「 OK ボタンを押すとダイアログのプロパティ、つまり各情報が表示されま
す。この各情報の、【一般】タブの、下から2番目に【ユーザーデータ】って
いう項目があると思います」
『うん、今は 00000000 になってる』
「これが実は、ウィンドウの【 32 ビット整数値】なんです」
『あ、これがそうなんだ! そか、 Spy++ を使えば見られるってわけね』
「この値は最初は 0 になってます。で、ここでダイアログを使ってみます。
Version 18.02 ( No.389 ) を参考に、 1 + 2 = 3 を計算してみて」
『 1 入力して、 2 入力して、 = ボタン押してと。うん、結果 3 が表示さ
れたよ』
『で、さっきの修正でこの 3 が、ダイアログにセットされたはずです』
『そか、それをさっきの SetWindowLong() 関数でしたんだもんね』
「それを確認してみましょう。先ほどの【ユーザーデータ】が表示されてい
るダイアログで、下にある【最新表示】ボタンを押してください」
『ぽちっと。あ、【ユーザーデータ】が 00000003 になった!!』
「このように、 SetWindowLong() 関数を使うことで、ウィンドウに好きな
値をセットして使うことができるわけです」
『なるほどねー』
「ちなみに、この仕組みは Windows でよく使われています。たとえば
Version 8.12 ( No.154 ) で TVITEM 構造体を紹介しました」
『ツリービューコントロールで使う構造体ね』
「この TVITEM 構造体は lParam メンバ変数を持っていて、これが実は、こ
の構造体で自由に使える 32 ビット整数値なんです」
『あ、さっきのウィンドウのと同じなんだ!』
「こんなふうに、 Windows システム内のオブジェクト、つまりウィンドウ
やこのツリービューコントロールのアイテムといったものは、たいがい
〈自由に使える 32 ビット整数値〉を持っているので」
『それをうまく使えば、それぞれにいろんな情報持たせることができる!』
「ということ。これをうまく使えるといろんなことができるから憶えておい
て。さて、では最後に、これをどう使って、メッセージを普通のメンバ関数
で受け取るようにするのか説明します」
『おー』
「これまでの方法は、ウィンドウとウィンドウプロシージャが以下のような
関係になっていました」
┌───────────┐ ┌───────────────┐
│ ウィンドウ │ │ ウィンドウプロシージャ │
│ │ │ (普通の関数 or │
│ ※イベント発生!※→→→│ static メンバ関数) │
└───────────┘ └───────────────┘
「で、このままウィンドウプロシージャ内で処理をしていたわけだけど、
これだと普通の関数や static メンバ関数だから、情報が増えるとそれだけ
グローバル変数が増えていくから大変です」
┌───────────┐ ┌───────────────┐
│ ウィンドウ │ │ ウィンドウプロシージャ │
│ │ │ (普通の関数 or │
│ ※イベント発生!※→→→│ static メンバ関数) │
└───────────┘ └────↓──────────┘
┌──────────────────↓──────┐
│ グローバル変数 ↓ │
│ ┌────────────────────┐ │
│ │ ウィンドウに結びつけられた変数 │ │
│ └────────────────────┘ │
└─────────────────────────┘
「このグローバル変数は、情報が増えたり、ウィンドウが増えたり、
ウィンドウプロシージャが増えたりすると管理がすごく大変になります」
『? ウィンドウとウィンドウプロシージャって分けて考えるものなの?』
「もちろん、 Version 5.23 ( No.088 ) を思い出して、
ウィンドウプロシージャはウィンドウクラスを登録しなきゃ使えないでしょ、
だからウィンドウを作るたびにほいほい作れるものじゃないんです」
『そういえばそうだね……ってことは、同じ種類のウィンドウをいっぱい
作っても、ひとつのウィンドウプロシージャでメッセージ受け取らなきゃ
いけないの? それって大変じゃない?』
「大変です。さっきの図で言えばこうなります」
┌───────────┐
│ ウィンドウ1 │
│ │ ┌──────────────┐
│ ※イベント発生!※→→→→→│ ウィンドウプロシージャ │
└───────────┘ │ (普通の関数 or │
┌───────────┐ →→│ static メンバ関数) │
│ ウィンドウ2 │ ↑ └────↓─────────┘
│ │ ↑ ↓
│ ※イベント発生!※→→→ ↓
└───────────┘ ↓
┌────────────────────↓──────┐
│ グローバル変数 ↓ │
│ ┌───────────┐┌───────────┐│
│ │ ウィンドウ1の変数 ││ ウィンドウ2の変数 ││
│ └───────────┘└───────────┘│
└───────────────────────────┘
「こういった変数を、ウィンドウハンドルで区別して使い分ける必要がある
わけです」
『うわ、面倒そう!』
「というわけで次回に続く!」
/*
Preview Next Story!
*/
『ウィンドウにこんな微妙な機能があったとは』
「微妙じゃないよ、いろんな使い道があるんだから」
『たとえば?』
「ポインタ入れてクラスと結びつけたり」
『他には?』
「……ポインタ入れて構造体と結びつけたり」
『…………』
「というわけで次回」
< Version 18.05 ウィンドウプロシージャをメンバ関数にする! >
『につづく!』
「というわけでポインタは万能なんです!」
『話すり替わってる!』