ウィンドウメッセージ処理の基礎

ウィンドウメッセージ――これは、Windowsの特徴の1つであるGUIを実現するために不可欠なものです。従来のHSPプログラミングではメッセージに対しての処理というものを行うことがありませんでしたが、本格的にWindows APIプログラミングを行っていくには、メッセージ処理が欠かせません。Windows APIの提供するさまざまな機能には、このメッセージに対しての処理ができないと意味をなさないものが多数存在するのです。逆に言えば、メッセージ処理によって機能を格段に拡張することができるようになる、ということです。

ウィンドウメッセージとメッセージ駆動方式

ウィンドウメッセージイベントメッセージ、または単にメッセージとも呼ばれる)は、Windowsがアプリケーションのウィンドウに対して送る通知のことです。ユーザーがマウスやキーボード入力を行ったとき、あるいは、ボタンやメニューを押したとき、これらの動作はまずOSであるWindowsによって検出されます。その後、Windowsはこれをそれぞれメッセージとしてアプリケーションに知らせるのです。アプリケーションは、このメッセージによってユーザーがどのような入力を行ったのかを知ることができますし、そのメッセージに応じてさまざまな処理をすることができるのです。

ウィンドウがメッセージが受け取るのは、そのウィンドウに対してメッセージを発生させるものがあるからです。それは、必ずしも上で述べたようにWindowsによるものだけではありません。メッセージの送り主がメッセージを発生させることを「メッセージを送る(送信する)」あるいは「メッセージをポストする」といいます。(「送信」と「ポスト」の詳しい違いは後述。)

ウィンドウに送られるメッセージにはいくつかの種類があります。

どのメッセージがどのような意味を持つかは、Windowsによって決められています。メッセージの種類は全部で1000種類近くあるともいわれています。

メッセージの実体は、あらかじめ決められている整数値です。しかし、プログラミングにおいてこれらの値(メッセージコード)をそのまま取り扱ったのでは分かりづらいので、C/C++などのプログラミング言語では“WM_”などで始まる定数名として定義されています。すなわち

#define WM_CREATE     0x0001

などというようにして、メッセージコードに対応した名前がつけられているのです。(C/C++における#defineはHSPのものとほぼ同じ働きをします。) HSPのスクリプト中でも、上記のように定数名を定義してそれを使用した方が分かりやすいでしょう。もちろん、HSPでは、#defineの代わりに#constを使用することもできます。

以下に、代表されるメッセージのほんの一部を紹介しましょう。

定数名 意味
WM_CREATE 0x0001 ウィンドウが作成された
WM_DESTROY 0x0002 ウィンドウが破棄されようとしている
WM_PAINT 0x000F ウィンドウを再描画する必要がある
WM_SIZE 0x0005 ウィンドウサイズが変更された
WM_LBUTTONDOWN 0x0201 マウスの左ボタンが押された
WM_LBUTTONUP 0x0202 マウスの左ボタンが離された
WM_LBUTTONDBLCLK 0x0203 マウスの左ボタンがダブルクリックされた
WM_RBUTTONUP 0x0204 マウスの右ボタンが押された
WM_RBUTTONUP 0x0205 マウスの右ボタンが離された
WM_RBUTTONDBLCLK 0x0206 マウスの右ボタンがダブルクリックされた
WM_KEYDOWN 0x0100 キーが押された
WM_KEYUP 0x0101 キーが離された
WM_CHAR 0x0102 文字が入力された

一般的なWindows GUIアプリケーションは、普段は停止しており、ユーザー入力があった場合など、上記のさまざまなウィンドウメッセージを受け取ったときに処理を開始するという方式をとっています。このような処理の仕方はメッセージ駆動方式あるいはイベント駆動方式などと呼ばれています。

ウィンドウプロシージャとメッセージ処理

ウィンドウプロシージャ

さて、ウィンドウにメッセージが送られたとき、Windowsは『ウィンドウプロシージャ』と呼ばれるコールバック関数(Windowsから呼び出される、アプリケーションによって実装されている関数)を呼び出します。ウィンドウプロシージャとは、アプリケーション自身が持っているメッセージ応対用のプログラムコードのことで、ここではメッセージに対してのさまざまな処理が行われます。

ウィンドウプロシージャでは、マウスクリックされた、キー入力された、ボタンが押されたなど、さまざまなメッセージに対してそれに対応する処理を行います。もちろん、HSP内部にもウィンドウプロシージャは存在していて、さまざまな処理をしているのです。例えば、ウィンドウのタイトルバーをドラッグすることでウィンドウを移動したり、ウィンドウ境界をドラッグしてウィンドウサイズを変更したりできます。ウィンドウが前面に出れば自動的に再描画されますし、×ボタンを押せば終了(あるいはonexit命令で指定されているラベルのスクリプトを実行)します。他にも、ウィンドウ上でマウスが動いたときにはシステム変数mousex, mouseyの値が自動的に変わりますし、コンボボックスやリストボックスの選択項目を変更すると割り当てられた変数の値が自動的に変更されます。これらのことは、すべてウィンドウプロシージャで行われているのです。(実際には、ウィンドウの移動やサイズ変更などのようにどのアプリケーションにも備わっている機能はWindowsが提供しているもので、アプリケーションのウィンドウプロシージャの中からさらに、Windowsが提供する『デフォルトウィンドウプロシージャ』という関数を呼び出すことで実現されています。)


ウィンドウプロシージャには、パラメータとして、メッセージコードのほかに2つの値が渡されます。これらの値は、wParamパラメータおよびlParamパラメータと呼ばれており、メッセージの付加情報として送られてくるのです。

wParamパラメータやlParamパラメータとして渡される情報はメッセージの種類によって異なります。例えば、マウスクリックを知らせるメッセージでは現在のマウスカーソルの座標などが、キーが押されたことを知らせるメッセージでは押されたキーコードなどの情報が、これらのパラメータとして渡されてくるのです。

メッセージの種類によっては、wParamとlParam両方のパラメータを使って付加情報を渡すものや、どちらか片方のパラメータしか使わないもの、あるいは、付加情報がないためにどちらのパラメータも使わないメッセージもあります。通常、使われないパラメータには常に0が渡されます。

HSPのメッセージ割り込み処理

以前のHSP2では、ウィンドウメッセージに対しての処理をするための機能が提供されていませんでした。そのため、HSPの本来のウィンドウプロシージャを、HSP拡張プラグインが提供する別の新しいウィンドウプロシージャに置き換えてしまう『ウィンドウのサブクラス化』という処理を行うことによって、メッセージ処理を実現させていました。

HSP3では、任意のウィンドウメッセージに対する割り込み処理がサポートされています。この機能は、指定されたウィンドウメッセージを受け取ったときに、指定されたラベルのスクリプトを実行するというものです。

HSPのウィンドウがメッセージを受け取ってウィンドウプロシージャが呼び出されたときに、任意のスクリプトが実行されるようにするには、あらかじめoncmd命令を使って、メッセージコードと実行させるラベルを設定しておかなければいけません。

oncmd goto *label, code
oncmd gosub *label, code
label: ラベル名
code: メッセージコード

この命令を実行した後は、codeで指定されたコードのウィンドウメッセージをHSPウィンドウが受け取ったときに、labelで指定されたラベルにジャンプ(またはサブルーチンジャンプ)します。gotoをともに指定(または省略)した場合には通常のジャンプになります。また、gosubをともに指定した場合はサブルーチンジャンプとなり、メッセージ処理の後は、もともと実行されていた所に戻ります。

oncmd命令による割り込みメッセージコードの登録は、描画中のウィンドウのみに対して行われます。複数のウィンドウを作成しており、それぞれのウィンドウでメッセージ処理を行いたい場合、それぞれ個別にoncmd命令を実行しなければいけません。

oncmd命令実行時には、常にgosubでサブルーチンジャンプを指定することを推奨します。ほとんどの場合、ウィンドウメッセージに対する処理を行うのは、スクリプト中のstop命令かwait命令かawait命令によって実行待機中であるときに限られます。oncmd命令でgoto指定を用いる場合、スクリプトにwait命令やawait命令が使われていなければ問題にならないかもしれませんが、これらの命令が使われているときには、wait/awaitによる待機中にメッセージを受け取ると、wait/await以降の処理が実行されなくなってしまいます。gosub指定を用いている場合には、メッセージ割り込み処理が終わった後でwait/await以降の処理が再開されるようになるのです。

gosub指定を推奨するもう1つの理由は、任意の戻り値を返すことができるようにするためです。ウィンドウプロシージャは関数であり、メッセージ処理の結果を関数の戻り値として返さなければいけません。goto命令を使用する場合、割り込み処理の後でデフォルトの(割り込みを行わない場合と同じ)メッセージ処理が行われ、戻り値もデフォルトのものが使用されます。一方、gosubによる割り込みの場合には、return命令によってウィンドウプロシージャの戻り値を指定することができるようになります。例えば、

return 0

とすることによって、ウィンドウプロシージャの戻り値として0が返されます。大部分のメッセージでは、アプリケーションが処理を行ったら、戻り値0を返さなければいけません。一部のメッセージでは、それ以外の戻り値を返さなければいけない場合があります。これについては、Windows APIの各メッセージに関するリファレンスを参照してください。

ただし、上記のような形で明示的に戻り値の値を指定した場合には、デフォルトの(割り込みを行わない場合と同じ)処理は行われなくなります。gosub指定による割り込みを行った際に、割り込み処理の後でデフォルトの処理も行いたい場合には、単純に

return

と、パラメータなしのreturn命令で処理を返す必要があります。この場合には戻り値もデフォルトのものになります。HSPが内部で処理するいくつかのメッセージでは、デフォルトの処理を行わないと正常に動作しなくなってしまいますので、注意してください。


oncmd命令でメッセージを登録することによって、そのメッセージを受け取った時に、指定されたラベルにジャンプして、それ以降の処理が行われることになります。指定されたラベルにジャンプしたとき、システム変数iparamにはメッセージコードの値が格納されています。また、システム変数wparamにはウィンドウプロシージャのwParamパラメータとして渡された値が、システム変数lparamにはウィンドウプロシージャのlParamパラメータとして渡された値が、それぞれ格納されています。メッセージ処理を行う際には、これらの値を使用することになります。

その他に、メッセージを受け取ったウィンドウのIDを調べるのにginfo関数を使用することができます。このときのパラメータには24を指定します。

id = ginfo(24)      ; メッセージを受け取ったウィンドウのID

また、標準マクロとしてginfo_intidマクロが定義されているので、これをginfo(24)の代わりに使用することもできます。

メッセージ処理で注意すべきことがあります。メッセージ処理実行中はwait命令やawait命令を実行することは避けるべきです。メッセージ処理のラベルにジャンプしたら、return命令(gosub指定の時)またはstop命令(goto指定の時)まで、停止せずに処理を行うようにしたほうが無難です。

メッセージ処理の例

では、実際にメッセージ処理を行うサンプルを実行させてみましょう。ここでは、ウィンドウがアクティブ化/非アクティブ化されるときに送られるWM_ACTIVATEメッセージに対しての処理を行ってみます。

WM_ACTIVATE - ウィンドウのアクティブ状態の変化

ウィンドウのアクティブ状態が変化するとき、すなわち、ウィンドウがアクティブ化または非アクティブ化するとき、ウィンドウはWM_ACTIVATEメッセージを受け取ります。WM_ACTIVATEメッセージのメッセージコードは0x0006です。

WM_ACTIVATEメッセージで渡されるwParamパラメータの下位ワードの値はアクティブ化フラグです。この値は、以下のいずれかになります。

定数名 意味
WA_INACTIVE 0 非アクティブ化されます。
WA_ACTIVE 1 マウスクリック以外の方法(例えば、キーボードインターフェースによる選択や、SetActiveWindow関数などによるもの)でアクティブ化されます。
WA_CLICKACTIVE 2 マウスクリックによってアクティブ化されます。

wParamパラメータの上位ワード値はウィンドウの最小化状態を表します。この値は、ウィンドウが最小化されている場合は0以外の値に、最小化されていない場合は0になります。

lParamパラメータの値は、このウィンドウに代わってアクティブ(または非アクティブ)になったウィンドウのハンドルになります。

サンプルスクリプト

スクリプトは非常に簡単で、oncmd命令によりメッセージコードとジャンプ先ラベルの登録をしたら、すぐにstop命令で停止させます。アクティブ状態の変化によってウィンドウにWM_ACTIVATEメッセージが送られた時点で、ラベルへジャンプされます。

ジャンプ先のラベルでは、wParamパラメータの値(システム変数wparam)から下位ワード値を取り出し、その値が0 (WA_INACTIVE) のときには非アクティブ化されたと判断し、それ以外の値のときはアクティブ化されたと判断しています。

; 割り込み処理メッセージの登録
#define WM_ACTIVATE  0x0006
oncmd gosub *OnActivate, WM_ACTIVATE
stop

*OnActivate
; ======== WM_ACTIVATE を受け取ったときの処理 ========
; アクティブ化フラグ (wParam の下位ワード)
fActive = wparam & 0xFFFF
if fActive {
    mes "アクティブ化されます"
} else {
    mes "非アクティブ化されます"
}
return 0