Windows アプリケーションでマウス操作を使う時が出て来ました。
何気なく使っていた WM_LBUTTONDOWN ( 左ボタンが押された ) 等のウィンドウメッセージ。
折角なので マウス操作、キーボード操作、ゲームパッド等 など,,
について色々と調べてみました。
*************************************************************************************
まずはおなじみウィンドウメッセージ。
OSから来たメッセージ ( ウィンドウが閉じられた~。 キーボードが押された~ など )
をプログラムが解釈してウィンドウプロシージャに送ってくれます。
そして、ウィンドウプロシージャ内で僕らプログラマーが switch文でメッセージごとにそれぞれ処理を書いていく感じですね。
/**
* ウィンドウプロシージャー
* OSから来るメッセージを処理するコールバック関数です。
* [引数]
* hWnd : ウィンドウハンドル
* msg : メッセージ
* wParam : メッセージの最初のパラメータ
* lParam : メッセージ二番目のパラメータ
* [戻り値]
* メッセージ処理結果
*/
LRESULT CALLBACK WindowProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
//===================================
// メッセージ処理
// メッセージはswitchで処理すると簡単
//===================================
switch ( msg )
{
case WM_CLOSE: //** ウィンドウが閉じられた
case WM_DESTROY: //** 誰かに殺された
case WM_QUIT: //** 終了メッセージ
// アプリケーションを終了する
PostQuitMessage( 0 );
break;
//** キーが押された
case WM_KEYDOWN:
// 押されたのはESC?
if ( wParam == VK_ESCAPE )
// アプリケーションを終了する
PostQuitMessage( 0 );
break;
//** ユーザー指定コマンド
case WM_COMMAND:
break;
//** システムコマンド
case WM_SYSCOMMAND:
switch ( wParam )
{
// スクリーンセーバーとモニタ電源OFFの防止
case SC_SCREENSAVE:
case SC_MONITORPOWER:
return 0;
}
break;
}
//** 興味ないイベントは全部 DefWindowProc関数に処理させる
return DefWindowProc( hWnd, msg, wParam, lParam );
}
その中に
・マウスの左 or 中央 or 右 ボタンが押された。 もしくは離された。
・マウスが移動した。
などのイベントもあります。 ↓一覧
メッセージ | 意味 |
---|---|
WM_LBUTTONDOWN | 左ボタンを押す |
WM_LBUTTONUP | 左ボタンを離す |
WM_MBUTTONDOWN | 中央ボタンを押す |
WM_MBUTTONUP | 中央ボタンを離す |
WM_RBUTTONDOWN | 右ボタンを押す |
WM_RBUTTONUP | 右ボタンを離す |
WM_XBUTTONDOWN | XBUTTON1 または XBUTTON2 を押す |
WM_XBUTTONUP | XBUTTON1 または XBUTTON2 を離す |
コピペ最高ぉー! ウッヒョォー!
XBUTTON1 、 2 というのは マウスの横についている2つのボタンで、
サイトの 「戻る」「進む」等を行なってくれます。
ちなみに今僕のマウスにはついていません( ;∀;)
『”マウスが押された。” ってだけじゃいらねーよ!
こちとら座標がほしいんじゃ!!(威圧)』
はい。ごもっともです。
座標は ウィンドウプロシージャの引数、
↑ のコードで言うと lParam に入っています。
32bit の 上位16ビットに x 座標。 下位16ビットに y 座標が格納済みです。
これらを取り出す簡単な方法は次のマクロを使用します。
int x = GET_X_LPARAM( lParam );
int y = GET_Y_LPARAM( lParam );
--- (注) Windowsx.h ---
int y = GET_Y_LPARAM( lParam );
--- (注) Windowsx.h ---
コレで簡単に
マウスの「クリックされた時の座標」や「動いた時の座標」が取れるわけです。
//** 左ボタンが押された
case WM_LBUTTONDOWN:
break;
//** 左ボタンが離された
case WM_LBUTTONUP:
break;
//** 右ボタンが押された
case WM_RBUTTONDOWN:
break;
//** 右ボタンが離された
case WM_RBUTTONUP:
break;
//** マウスが動いた
case WM_MOUSEMOVE:
break;
この座標はアプリケーションウィンドウの
クライアント領域の左上から数えたピクセル数になります。
なかなか便利なものであるな。
なので
クライアント領域より左に行けば マイナス値が、
右に行けば クライアント座標より大きくなった値が入ってきます。
WM_XBUTTON_DOWN と WM_XBUTTON_UP は Xボタン1, 2
両方共このメッセージになるので、
今度は 上のコードでいう wParam で識別します。
UINT button = GET_XBUTTON_WPARAM(wParam);
if (button == XBUTTON1)
{
// XBUTTON1 がクリックされた
}
else if (button == XBUTTON2)
{
// XBUTTON2 がクリックされた
}
ダボォクリックに対応させるには、
ウィンドウクラスの
WINDCLASS wc;
wx.style = CS_DBLCLKS;
wx.style = CS_DBLCLKS;
を指定して作成すると..
例えば左ボタンをダブルクリックした場合、
WM_LBUTTON_DOWN → WM_LBUTTON_UP →
WM_LBUTTONDBLCLK → WM_LBUTTON_UP
WM_LBUTTONDBLCLK → WM_LBUTTON_UP
の順にメッセージが帰ってきます。
右ボタン, 中央, Xボタンも ~DBLCLK が付いて帰ってきます。
Window内でクライアント領域の外、
↑の画像だと水色の部分がマウスで左クリックされたときは
NC が 付いたメッセージで返され、 WM_NCLBUTTONDOWN となります。
他のボタンでも一緒です。 ただ NC が付く。
駄菓子菓子!!
WM_NCMOUSEMOVE などで座標を受け取った場合、
この座標はクライアント領域から数えたピクセルの数( クライアント座標 )ではなく、
ディスプレイの左上から数えたピクセル数( スクリーン座標 )になる!!
こらびっくりやー(ヽ´ω`)
取り敢えずここまでで、一個プロジェクトを作ってみました。
マウスをクライアント領域内で動かすとクライアント領域の座標をタイトルバーに表示し、
Window上ではスクリーン座標をタイトルバーに表示します。
左クリックされたらウィンドウが半透明、無透明が交互に切り替わります。
右クリックの場合、クライアント領域のみ透明化します。
【 (URL) WinAPI -> MouseTest_01.zip 】
【 (URL) MouseTest_01.txt 】
***********************************
※ Windowの透明化
方法は二種類ある。
① ウィンドウクラスを作成する時に CreateWindowEX で作成し、
引数の一番目 dwExStyle に透明化を行うよう WS_EX_LAYERED も指定する。
② CreateWindow で作成後、 SetWIndowLong で ES_EX_LAYERED を新たに指定する。
自分は、
自分のプロジェクトが CreateWindow で作成していたので、後者を選択しました。
---②の場合---
スタイルを上書きされるので前のスタイルを覚えておく。
LONG style = GetWindowLong( hWnd, GWL_EXSTYLE );
2番目の引数を GWL_EXSTYLE にしないと表示がおかしくなりました。
で、スタイルを変更
SetWIndowLong( hWnd, GWL_EXSTYLE, WS_EX_LAYERED | style );
これで透明化の準備ができたので、透明化する。
これは SetLayeredWIndowAttributes を使います。
SetLayeredWIndowAttributes(
HWND hWnd, // ウィンドウハンドル
COLORREF crKey, // 指定した色を透明化する
BYTE bAlpha, // ウィンドウ全体を透明化する ( 0 ~ 255)
DWORD dwFlags // 第二引数か第三引数どちらを使用するか。
);
HWND hWnd, // ウィンドウハンドル
COLORREF crKey, // 指定した色を透明化する
BYTE bAlpha, // ウィンドウ全体を透明化する ( 0 ~ 255)
DWORD dwFlags // 第二引数か第三引数どちらを使用するか。
);
4番目の引数で
第2引数を使用するなら LWA_COLORKEY を
第3引数を使用するなら LWA_ALPHA を使用します。
***********************************
まだまだウィンドウメッセージ関連でありまして...
Window からマウスが出てしまったらどうなるか?
調べた結果...
反応なし。
WM_MOUSEMOVE とか死んでました。
そして最も HOW!? と感じたのが、、、
クライアント領域内で左クリックをしてそのまま Window 外 に出たとします...
そしてWindow外でマウスから手を離した場合・・・
WM_LBUTTONUP は呼ばれません。
( ゚д゚ )
これは うーん となりました。
正直、 ”Window外に出た” という情報と
"Window外でボタンが離された" という情報が欲しかったからです。
"Window外に出た"
そんなの WM_MOUSEMOVE で取得した座標、
X または Y が ”0” か ”設定したWindowの大きさと同じ” になった時に通知するようにしたらいいじゃん。
それがそうともいかず、
====================================
① 実行中に Windowの大きさを変えた時の場合はどうするか。
② 0 や 800( Windowの幅 ) などの座標値が来ずにクライアント領域から出てしまった場合。
====================================
問題が出てきてしまいます。
こらめんどくせぇ。
そう思って色々調べてたら
"ウィンドウ外に出てもマウスを追跡する機能"
が存在しました!(`・ω・´)
// ウィンドウハンドルを指定することで
// ウィンドウから見たマウスをいつでもどんなときもキャプチャ( ウィンドウメッセージを送る )する。
SetCapture( HWND hWnd );
// ウィンドウから見たマウスをいつでもどんなときもキャプチャ( ウィンドウメッセージを送る )する。
SetCapture( HWND hWnd );
これでクライアン領域外に出たマウスを追跡してくれれば
マイナスの値やウィンドウの幅高さ以上の座標値。
また、
ウィンドウの外でマウスが離されても WM_LBUTTONUP が来てくれそうです。
① の問題は GetClientRect で毎回クライアント領域を取得して処理をかければ大丈夫でした(´・ω・`)
この SetCapture
Window内にマウスがあろうとなかろうとずっと追跡してくれるすぐれものですが、、、
多用すべきではない。( MSDN )
使わない時( マウスがクリックされていないのに座標を追跡するなど )でも
がんがんメッセージを送ってくる。
= パフォーマンスに関わるので、、、
通常は
ボタンが押された時( WM_LBUTTONDOWN )に
SetCapture( hWnd );
を呼び出して、ボタンが離されたら、
// キャプチャを解除
ReleaseCapture();
を行う。
この関数を WM_LBUTTONUP などに記述して必ず解除してあげましょう。
コレを追加したプロジェクトを上げてみます。
今回はシンプルに座標をタイトルバーに表示するだけです。
左ボタンを押さずに window外 に出てもタイトルバーは何も変わりませんが、
押したままwindow外に出た場合、 座標系が変化し続けます。
【 (URL) WinAPI -> MouseTest_02.zip 】
【 (URL) MouseTest_02.txt 】
==========================================
こんなかんじでウィンドウメッセージのマウス操作は終了してみます。
ここで疑問に思ったのが・・・
あれ?
マウスを左クリックした状態で、マウスを動かさずに放置した場合はどうなるんだろう・・・
結果 ↓
何もメッセージ来ません。
これですよ・・・
僕としては押している間動いてなくてもリピートして欲しかったのですが、
WM_**** ではどうも無さそう・・・(´;ω;`)
// キーの処理を行う関数
GetKeyState( ・・・ );
で、ゲームループ内でキーボードのPUSH情報やマウスのクリック情報は取得できますが、
これを使ってマウスを調べた場合、座標がない。。。。
ゲームループ内で取得できるのはすごい嬉しかったんですが・・・
それで DirectInput とか調べてたんですが、
MSDNによると、DirectInputは
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
これは推奨されなくなりました。
ウィンドウメッセージを利用してください(本気)
XBOX360 ?
XInputを利用してください。
( XBOX系のコントローラーを簡単に読みこんでくれる )
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
と書いてました・・・ どないせいっちゅうねん(´・ω・`)
DirectInput は 様々なデバイスの対応をしている。
XInput は XBOXデバイス のみ。
しかし DirectInput は ウィンドウメッセージを推奨していると・・・・
イェア!!! (*´д`*)
しばらくコレに関しては自分の中で結果が出るまで放置することにしよう ( ーωー )
マイクロソフトは DirectX に力を全く入れなくなった気がする。
これは C# に移行して XNA を使えということか・・・
もしくは OpenGL
おわり