
メニューを表示させて、アイテムが選択されている状態にあるときに、そのアイテムの説明をウィンドウ下端のステータスバーに表示できたらいいですね。今回は、メニューアイテムのヘルプテキストをステータスバーに表示してみるようにします。
一般に、メニューを開いている状態では、スクリプトの実行が止められてしまいます。そのため、その間に送信されてくる、選択メニューアイテムが変わったことを通知するメッセージが届いても、 hsgetmsg.dll 等を使ってスクリプトで処理をするということができませんでした。
そこで、この動作を行なうためには、ウィンドウをサブクラス化して、選択メニューアイテムが変わったことを通知するメッセージを処理することのできるウィンドウプロシージャに置き換えてやる必要があります。今回はこの方法を説明します。
ウィンドウメッセージの割り込み処理が実現されれば、これらの処理をスクリプト中で行なうことができるようになると思われますが、 HSP 2.55 の時点では、サブクラス化を行なわなくてはなりません。
メニューアイテムの選択状態が変わると、そのメニューを持つウィンドウ(オーナーウィンドウ)には WM_MENUSELECT メッセージが送信されます。
#define WM_MENUSELECT 0x011F
WM_MENUSELECT
uItem = LOWORD( wParam ); // メニューアイテム
uFlags = HIWORD( wParam ); // メニューフラグ
hMenu = (HMENU)lParam; // メニューハンドル
uItem パラメータは、選択アイテムがサブメニューを持つ場合には選択アイテムの位置のインデックスが、そうでない場合(コマンドアイテムの場合)にはアイテムIDが指定されます。
uFlags パラメータは、選択アイテムの属性をあらわすフラグが指定されます。特に、選択アイテムがサブメニューを持つ場合には MF_POPUP メニューを含んでいます。
hMenu パラメータは、メニューハンドルが指定されます。
メニューが閉じられた場合には、 uFlags パラメータには 0xFFFF が、また uFlags パラメータには NULL が指定されるます。
今回はメニューアイテムのヘルプテキストをステータスバーに表示します。それまでステータスバーに表示されていた内容を変えてしまわないようにするためには、シンプルモードステータスバーを利用するのが最適ですね。
メニューアイテムが選択された状態になったら、ステータスバーをシンプルモードに変更して、ヘルプテキストを設定します。そして、メニューが閉じられた時点で、ステータスバーを非シンプルモードに戻せばよいでしょう。
ステータスバーのシンプルモードについては、『シンプルモードステータスバー』の項を参照してください。
スクリプト中で、メニューアイテムIDと対応するヘルプテキストをどのように保持していくかを考えてみましょう。最も簡単なのは、アイテムIDを格納する配列変数と、テキストを格納する文字列型配列変数を準備して、それぞれの要素で対応させるというものです。例えば、50個分のアイテムテキスト情報を格納するようにするには
dim selID, 50
sdim selText, 64, 50
というようにして、 selID.0 のアイテムIDを持つアイテムが選択されたときには selText.0 に格納された文字列がステータスバーに表示されるようにするのです。
今回はそのようにします。このとき必要となる情報は、まず、2つの変数のアドレスです。また、文字列変数の要素1つ分のサイズ(上のように書いた場合には 64 のこと)も必要な情報として渡さなければならないので注意しましょう。
まず、コード中に埋め込まなければならない情報について整理してみましょう。今回必要な情報は以下のようになります。
ここで、最後の「ステータスバーにテキストを表示する際の形式」というのは、 SB_SETTEXT メッセージの uType パラメータとして渡す値を示します。
いつもどおりに、構造体を定義して、必要な情報はすべてそれに格納してしまいましょう。必要となる情報を含む構造体を以下のように定義してみます。
typedef struct {
LRESULT (WINAPI *pfnCallWindowProc)(WNDPROC,HWND,UINT,WPARAM,LPARAM);
LRESULT (WINAPI *pfnSendMessage)(HWND,UINT,WPARAM,LPARAM);
WNDPROC pfnOrgProc;
int nItemCount;
UINT *pItemID;
LPSTR pItemText;
int nTextSize;
HWND hStatus;
UINT uStatusType;
} MENUHELP_DATA;
関数のアドレスはこれまでどおり FARPROC 型で指定してもいいのですが、コンパイラによる型チェックを厳密に行なうために、関数型や引数の型なども記述してあります。
新しいウィンドウプロシージャのソースコードです。
#include <windows.h>
#include <commctrl.h>
typedef struct {
LRESULT (WINAPI *pfnCallWindowProc)(WNDPROC,HWND,UINT,WPARAM,LPARAM);
LRESULT (WINAPI *pfnSendMessage)(HWND,UINT,WPARAM,LPARAM);
WNDPROC pfnOrgProc;
int nItemCount;
UINT *pItemID;
LPSTR pItemText;
int nTextSize;
HWND hStatus;
UINT uStatusType;
} MENUHELP_DATA;
LRESULT CALLBACK SubProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
int i;
LPSTR pText;
HWND hStatus;
UINT nItemID, *pID, uSBType;
LRESULT (WINAPI *pfnSendMessage)(HWND,UINT,WPARAM,LPARAM);
MENUHELP_DATA *volatile pTemp = (MENUHELP_DATA *), *pData = pTemp;
if (message == WM_MENUSELECT) {
pfnSendMessage = pData->pfnSendMessage; // SendMessage 関数のアドレス
hStatus = pData->hStatus; // ステータスバーのハンドル
if (HIWORD(wParam) == 0xFFFF && lParam == NULL) {
// メニューが閉じられた場合
// 非シンプルモードステータスバーにする
pfnSendMessage(hStatus, SB_SIMPLE, FALSE, 0);
} else {
// メニューアイテムが選択されている場合
// シンプルモードステータスバーにする
pfnSendMessage(hStatus, SB_SIMPLE, TRUE, 0);
// テキストが表示されていない状態にする
uSBType = SB_SIMPLEID | pData->uStatusType;
pfnSendMessage(hStatus, SB_SETTEXT, uSBType, (LPARAM)NULL);
if ( !(HIWORD(wParam) & MF_POPUP) ) {
// サブメニューを持たない(コマンドを持つ)アイテムの場合
nItemID = LOWORD(wParam); // アイテムID
pID = pData->pItemID;
for (i=0; i< pData->nItemCount; i++) {
if (pID[i] != nItemID) continue;
// ステータスバーにテキスト設定
pText = pData->pItemText + pData->nTextSize * i;
pfnSendMessage(hStatus, SB_SETTEXT, uSBType, (LPARAM)pText);
break;
}
}
}
}
return pData->pfnCallWindowProc(pData->pfnOrgProc, hWnd,
message, wParam, lParam);
}
埋め込まれる情報は、始めに定義した内部構造体(MENUHELP_DATA 構造体)のアドレス1つだけです。後でわかりやすいように、アドレスを としておきます。
ソースコードをコンパイルして、マシン語コードを生成します。VC++で行なった場合には、以下のようなコードが取り出せます。(取り出されるコードはコンパイラの種類や最適化設定などにより異なります。)
xdim subproc, 52 subproc.0 = $83ec8b55, $565310ec, $f045c757, $11111111, $81f0758b, $011f0c7d subproc.6 = $850f0000, $00000096, $8b10558b, $4e8b0446, $f045891c, $6610eac1 subproc.12 = $fffffa81, $89f44d89, $1375f855, $5d39db33, $530e7514, $04096853 subproc.18 = $ff510000, $3367ebd0, $016a53db, $00040968, $d0ff5100, $53207e8b subproc.24 = $00ffcf81, $01bb0000, $57000004, $f475ff53, $f6f055ff, $7510f845 subproc.30 = $0c468b3d, $00fc6583, $1055b70f, $85104e8b, $892b7ec0, $4d8bf84d subproc.36 = $741139f8, $fc45ff0e, $04f84583, $7cfc4539, $8b13ebed, $af0f1846 subproc.42 = $4603fc45, $53575014, $fff475ff, $75fff055, $1075ff14, $ff0c75ff subproc.48 = $76ff0875, $5f16ff08, $c2c95b5e, $00000010
次に、「」などとなっていた部分に正しい値(MENUHELP_DATA 構造体のアドレス)を格納しなければなりません。先頭からのオフセットを考えて、 memcpy 命令または ll_poke4 命令で格納します。上のコードの場合は、以下のようになります。
| オフセット | 仮の値 | 実際の値 |
|---|---|---|
| $C (12) | MENUHELP_DATA 構造体のアドレス |
では、スクリプトを書いてみることにしましょう。
メニューの作成が面倒なので、今回は hsgetmsg モジュール添付の menu.as で定義されているメニュー操作モジュール命令を使います。
#include "llmod.as"
#include "hsgetmsg.as"
#include "menu.as"
#include "xdim.as"
#define WM_COMMAND $111
; メニューアイテムIDを定義
#define CMD_OPEN $1001 ;「開く」
#define CMD_SAVE $1002 ;「保存」
#define CMD_QUIT $1003 ;「終了」
#define CMD_MESSAGE $1004 ;「メッセージ表示」
; メニューの作成
set_menu : hmenu = stat ; メニューバーの作成
set_popupmenu : hPopup = stat ; 「ファイル」メニュー
add_menu hPopup, "開く(&O)", CMD_OPEN
add_menu hPopup, "保存(&S)", CMD_SAVE
add_menu hPopup, ""
add_menu hPopup, "終了(&Q)", CMD_QUIT
addsub_menu hmenu, "ファイル(&F)", hPopup
set_popupmenu : hPopup = stat ; 「ヘルプ」メニュー
add_menu hPopup, "メッセージ表示(&M)", CMD_MESSAGE
addsub_menu hmenu, "ファイル(&F)", hPopup
draw_menu ; メニュー再描画
; ステータスバーの作成
dllproc "InitCommonControls", pm, 0, D_COMCTL
pm = 0,0,0,0,$50000003,0,0
_makewnd pm, "msctls_statusbar32"
hStatus = pm.0 ; ステータスバーのハンドル
; ステータスバーを3つのパーツに分ける
partspos = 50, 120, -1 ; パーツの位置
; SB_SETPARTS メッセージ送信
pm = hStatus, $404, 3
getptr pm.3, partspos
sendmsg pm
; パーツの表示テキストを設定
sdim msgtext , 128, 3
msgtext.0 = "パーツ1", "\tパーツ2", "\t\tパーツ3"
repeat 3
; SB_SETTEXT メッセージ送信
pm = hStatus, $401, cnt
getptr pm.3, msgtext.cnt
sendmsg pm
loop
; hsgetmsg.dll によるウィンドウのサブクラス化
set_subclass
hwnd = stat ; HSPウィンドウのハンドル
set_message WM_COMMAND ; 取得メッセージ設定
; メッセージパラメータ用変数
dup msg, msgval.1 ; メッセージが格納される変数
dup wprm, msgval.2 ; wParamパラメータが格納される変数
dup lprm, msgval.3 ; lParamパラメータが格納される変数
; 新しいウィンドウプロシージャのコード
xdim subproc, 52
subproc.0 = $83ec8b55, $565310ec, $f045c757, $11111111, $81f0758b, $011f0c7d
subproc.6 = $850f0000, $00000096, $8b10558b, $4e8b0446, $f045891c, $6610eac1
subproc.12 = $fffffa81, $89f44d89, $1375f855, $5d39db33, $530e7514, $04096853
subproc.18 = $ff510000, $3367ebd0, $016a53db, $00040968, $d0ff5100, $53207e8b
subproc.24 = $00ffcf81, $01bb0000, $57000004, $f475ff53, $f6f055ff, $7510f845
subproc.30 = $0c468b3d, $00fc6583, $1055b70f, $85104e8b, $892b7ec0, $4d8bf84d
subproc.36 = $741139f8, $fc45ff0e, $04f84583, $7cfc4539, $8b13ebed, $af0f1846
subproc.42 = $4603fc45, $53575014, $fff475ff, $75fff055, $1075ff14, $ff0c75ff
subproc.48 = $76ff0875, $5f16ff08, $c2c95b5e, $00000010
; メニューアイテムのヘルプテキスト
menunum = 4
dim selID, menunum
sdim selText, 64, menunum
selID = CMD_OPEN, CMD_SAVE, CMD_QUIT, CMD_MESSAGE
selText.0 = "ファイルをオープンします。"
selText.1 = "ファイルに保存します。"
selText.2 = "アプリケーションを終了します。"
selText.3 = "メッセージダイアログを表示します。"
; 内部構造体( MENUHELP_DATA 構造体)
; API 関数アドレスを取得
dll_getfunc procdata.0, "CallWindowProcA", D_USER
dll_getfunc procdata.1, "SendMessageA", D_USER
; 現在のウィンドウプロシージャアドレス取得
pm = hwnd, -4 : setwndlong pm, 1
procdata.2 = stat ; ウィンドウプロシージャアドレス
; ヘルプテキストを格納した変数の情報
procdata.3 = menunum ; 配列変数に格納されているアイテム情報の数
getptr procdata.4, selID ; アイテムIDを格納した配列変数アドレス
getptr procdata.5, selText ; テキストを格納した配列変数アドレス
procdata.6 = 64 ; テキストを格納した配列変数の1要素サイズ
procdata.7 = hStatus ; ステータスバーのハンドル
procdata.8 = $100 ; ステータスバー表示形式(SBT_NOBORDERS)
; MENUHELP_DATA 構造体のアドレスをマシン語コードに埋め込む
getptr p, procdata
memcpy subproc, p, 4, $C
; ウィンドウのサブクラス化
pm = hwnd, -4
getptr pm.2, subproc ; 新しいウィンドウプロシージャのアドレス
setwndlong pm
*mainloop
get_message
if msgval {
if msg == WM_COMMAND : gosub *lb_on_command
} else {
wait 10
}
goto *mainloop
*lb_on_command
; メニュー以外から送られた場合は何もしない
if lprm != 0 : return
itemid = wprm & $FFFF ; 選択されたメニューアイテムID
if itemid == CMD_OPEN {
dialog "*", 16
if stat {
dialog refstr+"を開きました"
}
}
if itemid == CMD_SAVE {
dialog "*", 17
if stat {
dialog refstr+"を保存しました"
}
}
if itemid == CMD_QUIT {
dialog "終了します", 0
end
}
if itemid == CMD_MESSAGE {
dialog "メニュー作成のテストです", 0, "メッセージ表示"
}
return