私はGUI環境で簡単にコマンドを送ることが出来るマウスジェスチャが大好きです。今回はそんなマウスジェスチャを作ってみましょう。
C言語(またはC++言語)でWIN32のプログラムを開発したことがある方。
少なくとも32BitのWindows環境が必要です。64Bit環境でのテストはしていませんが大丈夫だと思います。
まずは構想を練らなければなりません。今回はサンプルなのでこちらで決めさせて頂きます。
これだけですが、一応使用に耐えうる立派なマウスジェスチャ検出になります。
今回のサンプルでは一回一回の移動に対して移動量の比較を行います。何故かというと、斜めの入力を適切に処理するためです。
たとえば斜め移動を行ったときにこれをしないと二つの方向が交互に検出されてしまいます。ですのでそれを防ぐためにユーザがどちらへより動かしたかったのかを判定するのです。
今回重要な役割を果たすのが次のAPIです。
32BitWindowsでは自分のウィンドウの上のマウスの動きしか知ることが出来ません。ですが、マウスジェスチャではマウスがウィンドウ外に出てしまったときのことも考えなければなりません。そのため上の二つの関数を使うことになります。これらは「自分のウィンドウ内でボタンが押されたマスカーソルをボタンが押されている間だけ追跡することが出来る」関数です。
マウスジェスチャを行う上ではちょうど良いくらいの機能です。ちなみにキャプチャされた移動と、ボタンを放す動作は「WM_~」としてウィンドウプロシージャに送られてきます。
マウスジェスチャを検出するために作る構造体です。C++であればクラスにまとめるのがベストかと思いますがC言語との相互運用を考えて今回は構造体です。
//方向を表す列挙型 typedef enum tag_MGARROW{ MG_NONE = -1, //移動無し MG_UP = 0, //上に移動 MG_RIGHT =1, //右に移動 MG_DOWN = 2, //下に移動 MG_LEFT = 3 //左に移動 }MGARROW; //それぞれの方向への情報を持つ typedef struct tag_MGDIRECTION{ BOOL enable; //有効か? int length; //累計移動距離 }MGDIRECTION; //マウスジェスチャを行うための要の構造体 typedef struct tag_MOUSEGESTURE { BOOL enable; //有効か? int range; //判定距離 MGDIRECTION direc[4];//それぞれの方向の情報 //添え字の番号は「MGARROW」と対応する POINT old; //移動距離を出すための位置 }MOUSEGESTURE;
それぞれの方向に有効判定を付けたのは2連続を防止するためです。「int range」は「移動した」と判断するのに必要な距離です。「POINT old」は前回に判定を行った位置です。その位置を起点にして累計移動量を出していきます。
今回作る関数は下の4つだけです。
//マウスジェスチャのスタート void StartMouseGesture(MOUSEGESTURE *lpmg,HWND hwnd,int x,int y); //マウスジェスチャの終わり void EndMouseGesture(MOUSEGESTURE *lpmg); //マウスジェスチャの判定を行う。 MGARROW TestMouseGesture(MOUSEGESTURE *lpmg,int x,int y); //移動情報のリセット void ResetDirection(MOUSEGESTURE *lpmg);
再度のリセット関数は内部的に必要なるだけの関数なので使用者は使うことがありません。行っているのは「direc」の移動距離リセットと有効化です。
この関数は「WM_*BUTTONDOWN」に仕掛けます。
//マウスジェスチャのスタート void StartMouseGesture(MOUSEGESTURE *lpmg,HWND hwnd,int x,int y){ //有効化 lpmg->enable = TRUE; //リセット ResetDirection(lpmg); lpmg->old.x = x; lpmg->old.y = y; //押されている間だのキャプチャを開始 SetCapture(hwnd); }
マウスジェスチャ構造体、ウィンドウハンドル、位置を使ってマウスジェスチャの準備をします。また「SetCapture」を呼び出しています。
ここがマウスジェスチャの心臓部です。これは「WM_MOUSEMOVE」に仕掛けるのが妥当でしょう。
//マウスジェスチャの判定を行う。 MGARROW TestMouseGesture(MOUSEGESTURE *lpmg,int x,int y){ //有効なときだけ判定する。 if(lpmg->enable){ int ox = lpmg->old.x,oy = lpmg->old.y; int direc = (int)MG_NONE; //情報を入れ替えておく lpmg->old.x = x; lpmg->old.y = y; //移動量を判定して縦横どっちに動くかを判定 if(abs(ox - x) > abs(oy - y)){ if(ox > x){ lpmg->direc[MG_LEFT].length += ox - x; lpmg->direc[MG_RIGHT].length = 0; direc = (int)MG_LEFT; }else if(x >ox){ lpmg->direc[MG_RIGHT].length += x - ox; lpmg->direc[MG_LEFT].length = 0; direc = (int)MG_RIGHT; } }else{ if(oy > y){ lpmg->direc[MG_UP].length += oy - y; lpmg->direc[MG_DOWN].length = 0; direc = (int)MG_UP; }else if(y >oy){ lpmg->direc[MG_DOWN].length += y - oy; lpmg->direc[MG_UP].length = 0; direc = (int)MG_DOWN; } } //移動を検知したとき if(direc !=MG_NONE){ if(lpmg->direc[direc].enable && lpmg->direc[direc].length > lpmg->range){ ResetDirection(lpmg); //同じ向きが2度入力されないようにする。 lpmg->direc[direc].enable=FALSE; return (MGARROW)direc; } } } return MG_NONE; }
直感に反しないよう私なりに調整してあります。流れは次のようになります。
もっと良い工夫をお持ちの方もいると思います。色々な人に試して貰いながらどんな仕組みにするのかを決定するのが良いと思います。
ボタンが離されると、「SetCapture」の魔法が解けてしまいます。よって、「WM_*BUTTONUP」あたりにこの関数を仕掛けるのが妥当だと思います。
//マウスジェスチャの終わり void EndMouseGesture(MOUSEGESTURE *lpmg){ //マウスキャプチャの終わり ReleaseCapture(); lpmg->enable = FALSE; }
一応「ReleaseCapture」を呼び出しておいてください。
まずはどこかに「MOUSEGESTURE構造体」を作ってください。次に「ZeroMemory」で全体を初期化した後、「range」に適当な値をセットしてください。
そしたら、ウィンドウプロシージャの設定をします。「WM_*BUTTONDOWN」のなかで「StartMouseGesture」を、「WM_MOUSEMOVE」のなかで「TestMouseGesture」を、「WM_*BUTTONUP」のなかで「EndMouseGesture」を呼び出すようにセットしてください。
これで「TestMouseGesture」を呼び出すたびに「MGARROW列挙型」で値が帰ってくるようになります。
マウスジェスチャはアプリケーションに大きな魅力を付加します。使いこなせばボタンにカーソルを移動することなく素早く操作できるようにな
ります。
是非、実装を検討してみてください。