Lnks 技術情報



Lnks内部で利用している部分をちょこっと解説。
実装上問題がある可能性があるため過信は禁物。

また、#func,#cfuncでコールする関数の引数指定は、
hsp側が用意しているuser32.asなどの中身と異なるため注意。
なぜそうするかは、そうしたほうが分かりやすいからに尽きる。

ソース内のコメントにセミコロン(;)を用いずにダブルスラッシュ(//)を用いているのは、
ページ上で見た際の見やすさのためと、記述者の癖なので。

(1) ウィンドウのドラッグ移動

通常HSPのbgsrc命令で作成したウィンドウはドラッグ移動はできない。
もしドラッグ移動を望むのであれば、何かしらの方法でマウスクリックされた位置を
"ウィンドウ内部でなく、キャプションバーである"ように偽装する必要がある。

偽装方法としてはWM_NCHITTESTイベントのreturn値をキャプションバーの上であることを表す値を返すか、
ウィンドウ内のクリック動作時に、キャプションバーをクリックされたことにするイベントを送るかといった手段がある。

前者の場合、"HITTESTが偽装されているため、ウィンドウ内をクリックされたことを検出できない"という問題が出る。
そのため後者の方法をとることにする。

#define global WM_NCLBUTTONDOWN 0x00A1
#define global WM_LBUTTONDOWN 0x0201
#define global HTCAPTION 2

//oncmdによるマウスクリック検出
oncmd gosub *click , WM_LBUTTONDOWN

stop

//移動部分
*click
sendmsg hwnd,WM_NCLBUTTONDOWN,HTCAPTION,lparam
return

また、ウィンドウはディスプレイの外に出ないようになっている。(これがwin8で動作障害かもしれない。)
ウィンドウを移動する際、WM_MOVINGメッセージが来て、デフォルトの移動先が確認できる。
このデフォルトの移動先を捻じ曲げることで、
移動を制限したり移動するとウィンドウが拡大しながら動くウィンドウが作れる。
#define global WM_MOVING 0x0216

//oncmdによる移動検出
oncmd gosub *moving , WM_MOVING

stop

//移動先の検出
*moving
dupptr p1,lparam,4*4,vartype("int") // 4 * sizeof(LONG)
// p1 に移動先の座標(左上x、y、 右下x、yの順に入る)
// p1の中身を自由にいじって後はreturnする
return

(2) ウィンドウ内マウス移動関連の検出

ウィンドウ内でのドラッグを検知したい状況は珍しいことじゃない。ペイントソフトなどを作ればよくある。
クリック開始を検知する方法はonclickでもoncmdでもstickでもgetkeyでもいろいろあるが、
クリックを離すタイミングを感知できる方法はstick,getkeyあたりに限られ、常時監視する必要がある上に、
mousex,mouseyのシステム変数で位置を取得できないためginfoを使って取得し、相対座標に直すことになる。

常時監視しても問題ない状況ならいいが、そのためだけにループを組むと処理の負荷が気になるところ。
そんなことをしなくても、
API関数のSetCaptureを用いるとクライアント領域外にあるマウスに関するイベントを取得できる。
ただし、用がすんだらきちんとReleaseCaptureで解放してあげないとひどいことになります。

実用上は、マウスボタンが押された際(WM_LBUTTONDOWN)にSetCaptureを呼びだすことで、
ほぼ確実にマウスボタンが離されたときのイベント(WM_LBUTTONUP)が取得できる。
ドラッグの終了が分かればいいので、このときにReleaseCaptureを呼べばいい。
#uselib "user32.dll"
#func global SetCapture "SetCapture" int
#func global ReleaseCapture "ReleaseCapture"
#define global WM_LBUTTONDOWN 0x0201
#define global WM_LBUTTONUP 0x0202

//oncmdによるマウスクリック検出
oncmd gosub *md , WM_LBUTTONDOWN
oncmd gosub *mu , WM_LBUTTONUP

stop

// *md,*muに飛ぶとき、lparamにはマウス現在位置が入っている。
// mousex == ((lparam<<16)>>16)
// mousey == (lparam>>16)
*md
//ドラッグ操作の開始? クリックされたwindowにgselを合わせるのが本来。
SetCapture hwnd
return
*mu
//ドラッグ終了
ReleaseCapture
return
注意するべきは、クリック操作なのかドラッグなのかを判別するのはプログラム側で行う必要がある点である。
上のソースでは何も対処していない。



マウス操作のソフトで、クライアントウィンドウの外にマウスが移動した際、
最後に乗っていた場所を記憶してメニュー項目が点灯したままになったりすることがある。
これはmousex,mouseyの値が「最後にマウスがウィンドウ上にいた際の位置(ちょっと違うけど)」なため起こる現象である。

通常、ウィンドウ外にマウスが行ったことを感知するには、
ginfoを用いてマウス座標をウィンドウ相対座標に変換し、それをmousex,mouseyと比較することになる。
こうするとドラッグ操作と同様に、監視のためにループを回すことになってしまう。
だが、そんなことをしなくてもWindows側から通知してもらう方法がある。

通知はWM_MOUSELEAVEというイベントで、ウィンドウの上からマウスポインタが離れた際に発行される。
しかし、このイベントはTrackMouseEventというAPI関数で要求しないと発行してくれない。
このAPI関数の有効期間は、一度WM_MOUSELEAVEを発行するまでなので、
いつでもマウスポインタが離れたことを知りたいのであればマウスポインタがウィンドウ内に戻ってきた際に
もう一度呼ぶ必要がある。
#uselib "user32.dll"
#func global TrackMouseEvent "TrackMouseEvent" var
#define global WM_MOUSEMOVE 0x0200
#define global WM_MOUSELEAVE 0x02A3
#define global TME_LEAVE 0x02
#define global HOVER_DEFAULT 0xFFFFFFFF

//oncmdの指定
oncmd gosub *mm , WM_MOUSEMOVE
oncmd gosub *ml , WM_MOUSELEAVE

stop

*mm
dim p1,4
p1 = 4*4 , TME_LEAVE , hwnd , HOVER_DEFAULT
TrackMouseEvent p1
return
*ml
//マウスがウィンドウから離れたよ!
return
このサンプルではマウスがウィンドウ上に来れば1度は発行されるWM_MOUSEMOVEのイベント中にTrackMouseEventを呼んでいる。

(3) システムメニューの改変

システムメニューとは、ウィンドウの上部のバーや、タスクボタンの上で右クリックすると出るやつである。
改変に関しては探せば出てくるだろうけどせっかくなので。

システムメニューにはデフォルトで「元の大きさに戻す」や「最大化」などがあるが、
これらを消すと、その機能が使用不能になる。特に閉じれないのは致命傷になりかねない。
ちなみに、標準のHSPウィンドウは「移動」「最小化」「閉じる」「元のサイズに戻す」の4種が使える可能性がある。

流れとしては、GetSystemMenuを呼び、システムメニューのハンドルを取得。
DeleteMenuやInsertMenuItemAなどにハンドルを渡してメニューを改変、
最後にDrawMenuBarを呼んで完了となる。

最大化を消してみる例を出しておく。
#uselib "user32,dll"
#cfunc global GetSystemMenu "GetSystemMenu" int , int
#func global DeleteMenu "DeleteMenu" int , int , int
#func global DrawMenuBar "DrawMenuBar" int

#define global SC_SIZE 0xF000
#define global SC_MOVE 0xF010
#define global SC_MINIMIZE 0xF020
#define global SC_MAXIMIZE 0xF030
#define global SC_CLOSE 0xF060
#define global SC_RESTORE 0xF120

#define global MF_BYCOMMAND 0x0000

hMenu = GetSystemMenu(hwnd,0)
DeleteMenu hMenu,SC_MAXIMIZE,MF_BYCOMMAND
DrawMenuBar hwnd
DrawMenuBarなしでも動いたが・・・。

DeleteMenuの第3引数にMF_BYPOSITION(0x400)を指定すると上から何番目のメニューを消すという指定ができるが、
他のツール類によってシステムメニューが拡張されているケースがあるため、MF_BYCOMMANDのほうが安全だと思われる。
追加の際にはInsertMenu等の関数で行え、これを解釈するにはWM_SYSCOMMANDを確認する必要がある。


HSPで作成できるウィンドウにはいくつか種類があるが、
screen命令で作成できるウィンドウはシステムメニューを標準で利用できる。(ツールウィンドウであっても)
bgsrc命令で作成したウィンドウに関しては、システムメニューを持つものの、通常では出現させることができない。

bgsrcで作成したウィンドウが、他のウィンドウと同様にタスクバーを右クリックした際にメニューが出るようにするには、
そのウィンドウにWS_SYSMENUを付与すればいい。

WS_SYSMENUを付与するにはSetWindowLongを用いる。
#uselib "user32.dll"
#func global SetWindowLongA "SetWindowLongA" int , int , int
#cfunc global GetWindowLongA "GetWindowLongA" int , int
#define global GetWindowLong GetWindowLongA
#define global SetWindowLong SetWindowLongA

#define global GWL_STYLE 0xFFFFFFF0
#define global WS_SYSMENU 0x00080000


bgscr 0,320,240,,320,240//bgscrでウィンドウ作成

SetWindowLong hwnd,GWL_STYLE,WS_SYSMENU|GetWindowLong(hwnd,GWL_STYLE) // WS_SYSMENUの付与

stop
SetWindowLongの第3引数にはウィンドウスタイルを指定するが,変更元のウィンドウスタイルの値を利用して指定するのが一般的である。

(4) ショートカットファイルのリンク先を知る

ランチャーへの登録の際ドラッグドロップが利用できたら便利なことは当然である。
すると、ランチャーにショートカットファイルがドラッグドロップされる場合を考慮するべきである。
しかし、ddacceptにせよDragQueryFileAにせよ取得できるのはドロップインされたショートカットファイルのパスである。

それをランチャーにそのまま登録してしまうと、
「ランチャー導入したらデスクトップのショートカット撤去出来るよね」
という声に答えることは当然できない。撤去した時点でリンク切れである。

ショートカットファイルがドロップインされた場合の措置は、
上記のリンク切れを解消するのであれば良い。

解消方法として、Lnksではショートカット先を取得しそれを保持することにしている。
その方法はいくつかあるのだろうが、
COMオブジェクトであるIShellLinkを使ってショートカット先を取得する方法を選んだ。
// クラスIDの定義
#define global CLSID_ShellLink "{00021401-0000-0000-C000-000000000046}"
// インターフェースIDの定義
#define global IID_IShellLinkA "{000214EE-0000-0000-C000-000000000046}"
#define global IID_IPersistFile "{0000010b-0000-0000-C000-000000000046}"

#usecom global IShellLink IID_IShellLinkA CLSID_ShellLink
#comfunc global IShellLink_GetPath 3 var,int,int,int

#usecom global IPersistFile IID_IPersistFile
#comfunc global IPersistFile_Load 5 wstr,int

#define global STGM_READ 0


linkfilepath = "C:\\メモ帳.lnk" //ショートカットファイルのパス

newcom pLink,IShellLink //COMの準備
querycom pPerFile,pLink,IID_IPersistFile //COMの準備2
IPersistFile_Load pPerFile,linkfilepath,STGM_READ //ファイルの読み込み
sdim linkaimpath,1024 //リンク先を入れるデータ
dim p1,80// 要求データのメモリ
IShellLink_GetPath pLink,linkaimpath,1024,varptr(p1),0x0000 //データ読み出し
delcom pPerFile //用済みCOMの処分2(後に作成したものから消す)
delcom pLink //用済みCOMの処分

// これで linkaimpath にファイルパスが入る。しかし、うまくいかないファイルもある。
// そのフォローは・・・?



余談になるが、COMをうまく扱えばDirectXにアクセスしてDLLなしにDirect3Dを扱えたりする。

HSP側ではGUIDとして"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"という形で定義するが、
関数がGUIDを要求する際、REFIID型(GUID*型)で要求してるため、
引数指定をstrとして上の文字列を渡しても認識できない。
なので、str を GUIDが入っている配列 という変換コードを組んでおくと若干楽ができる。
COMを使う局面が多いソースでは変換関数を作る価値はあるのではないだろうか。

その他の情報は多分HSPの情報サイトを漁れば出るかと。

(C) 2013 すうがくかわーい\(^o^)/