Archives

April 2009 の記事

Windows7 のマルチタッチ系イベントには二通りあることを前回説明しました。
高度な方、WM_GESTURE 系についてもう少し詳しくさわってみます。

メッセージは WM_MOUSEMOVE のように座標の更新毎に多数送られてきます。
このとき一つの操作の組が GID_BEGIN と GID_END で囲まれます。
例えば PAN 操作の場合

GID_BEGIN
GID_PAN
GID_PAN
GID_PAN
GID_PAN
...
GID_END

といった感じに。
また別の操作が認識されれば、それらが GID_BEGIN~GID_END で囲まれます。
マウスでいえば LBUTTONDOWN / LBUTTONUP のようなもの。
タッチだと WM_TOUCHDOWN / WM_TOUCHUP でしょう。
開始と終了が明確にわかるようになっています。

ただ上の例だと最初と最後の GID_PAN 自身にも GF_BEGIN, GF_END フラグが
追加されているので、実は GID_BEGIN/GID_END を無視しても何も困りません。
フラグ値を追加して書き直してみます。

GID_BEGIN
GID_PAN  GF_BEGIN
GID_PAN
GID_PAN
...
GID_PAN
GID_PAN  GF_INERTIA
...
GID_PAN  GF_INERTIA
GID_PAN  GF_INERTIA|GF_END
GID_END

GF_INERTIA は PAN 操作のみ追加される慣性で、操作後もしばらくメッセージが
送られてきます。

前回も書きましたがマニュアルにはまだ間違いが含まれているので、仕様が確定
するまではヘッダファイルを見た方が確実です。ヘッダは WinUser.h です。

WM_GESTURE が送ってくる座標値はスクリーン座標です。
GESTUREINFO 構造体には対象となるウィンドウのハンドルが含まれていますが、
ウィンドウ内の座標系に変換する場合は自分で行う必要があります。

一本指のシングルタッチなどはマウスと同じ扱いなので、マウス系のメッセージを
処理する必要があります。よってフリック操作はマウス扱いです。

パンや回転、ズーム(2点間の距離)などの操作は結構誤動作というかノイズが入るようです。
極端に異なる値が来たときは無視するような処理が必要になるかもしれません。
また GID_ROTATE の最初のメッセージには回転値として不正な値が入っているようです。
GID_ROTATE かつ GF_BEGIN が立っている場合、ullArguments の下位 16bit を 0 と
見なした方がよいです。
回転しようとすると必ず反転してしまうおかしな症状に最初悩みました。

用途によるとは思いますが、やはり凝ったことを行いたい場合は WM_GESTURE ではなく
WM_TOUCH 系 (WM_TOUCHUP/WM_TOUCHDOWN/WM_TOUCHMOVE) を
使った方が良さそうです。
WM_GESTURE の場合操作判定が先に行われてしまうので、操作の位置を制限することが
難しくなります。
例えば特定のオブジェクトの上だけで回転やズームを行い、それ以外の座標では
常にパン操作と判定したい場合などあまりきれいに操作を区別することができません。

最初は簡単に見えたものの、実際にやってみるとまだまだ。
プログラムの方もいろいろと工夫が必要になりそうです。


関連エントリ
Windows7 Multitouch API


タッチ関連の API は 2種類あります。
WM_TOUCH 系と WM_GESTURE 系

WM_TOUCH 系の方が低レベルで、直接複数のタッチ座標を取り出すことが出来ます。
WM_GESTURE の方はいくつかの決まった操作を容易に受け取ることができます。

WM_TOUCH 系のメッセージを有効にするには RegisterTouchWindow() を呼び出します。
これを実行しておかないと WM_TOUCH~ が送られて来ません。

データを受け取るのは簡単です。

WM_TOUCHDOWN
WM_TOUCHUP
WM_TOUCHMOVE

などマウスとよく似ているメッセージが来るので、さらに
GetTouchInputInfo() を使って詳細な情報を読み取ります。


起動時の判定

// ハードがマルチタッチをサポートしているかどうか
int  value= ~GetSystemMetrics( SM_DIGITIZER );
if( !(value & 0xc0) ){
    RegisterTouchWindow( hwnd, 0 );
}

メッセージ

    case WM_TOUCHDOWN:
    case WM_TOUCHUP:
    case WM_TOUCHMOVE:
	WM_Touch( mes, wparam, lparam );
	return	FALSE;

読み出しの例

void WM_Touch( UINT mes, WPARAM wparam, LPARAM lparam )
{
    int	inputs= LOWORD( wparam );
    TOUCHINPUT	tbuf[TOUCHMAX];
    HANDLE	hinput= reinterpret_cast<HANDLE>( lparam );
    if( GetTouchInputInfo( hinput, inputs, tbuf, sizeof(TOUCHINPUT) ) ){
	TOUCHINPUT*	tp= tbuf;
	for( int i= 0 ; i< inputs ; i++, tp++ ){
	    ...
	}
    }
    CloseTouchInputHandle( hinput );
}

座標値そのままなので、これを元にズームや回転などの操作を検出するには
さらに一手間いります。


WM_GESTURE 系はそのあたりを簡単にしてくれます。
下記の操作が定義されています。

GID_ZOOM
GID_PAN
GID_ROTATE
GID_TWOFINGERTAP
GID_ROLLOVER

これらのメッセージは RegisterTouchWindow() を実行すると来なくなるので
WM_GESTURE 系を使う場合は RegisterTouchWindow() を実行してはいけません。

WM_GESTURE が送られてきたらさらに追加情報を受け取ります。

    case WM_GESTURE:
	WM_Gesture( mes, wparam, lparam );
	return	FALSE;


void WM_Gesture( UINT mes, WPARAM wparam, LPARAM lparam )
{
    GESTUREINFO	ginfo;
    memset( &ginfo, 0, sizeof(GESTUREINFO) );
    ginfo.cbSize= sizeof(GESTUREINFO);
    HGESTUREINFO	hgesture= reinterpret_cast<HGESTUREINFO>( lparam );

    if( GetGestureInfo( hgesture, &ginfo ) ){
        ...
    }
    CloseGestureInfoHandle( hgesture );
}

WM_GESTURE 系の仕様は若干変更があったようで、資料によっては記載内容が
異なっていることがあります。例えば今でも

MSDN WM_GESTURE Message

このページにある dwCommand は存在しておらず GESTUREINFO 構造体の
dwID のことだと思われます。
同じように dwArgument も 64bit の ullArguments に変更されているようです。

・dwCommand → GESTUREINFO dwID
・dwArgument → GESTUREINFO ullArguments
・lParam → GESTUREINFO ptsLocation

その他いくつか気が付いた点。

初期状態では GID_ROTATE などのメッセージが来ませんでした。
SetGestureConfig() を使って送って欲しいメッセージの登録が出来るようです。

const int	ConfigCount= 5;
GESTURECONFIG	config[ConfigCount];
memset( config, 0, sizeof(GESTURECONFIG) * ConfigCount );

config[0].dwID= GID_ZOOM;
config[0].dwWant= GC_ZOOM;

config[1].dwID= GID_PAN;
config[1].dwWant= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY
	|GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY
	|GC_PAN_WITH_GUTTER
	|GC_PAN_WITH_INERTIA;

config[2].dwID= GID_ROTATE;
config[2].dwWant= GC_ROTATE;

config[3].dwID= GID_TWOFINGERTAP;
config[3].dwWant= GC_TWOFINGERTAP;

config[4].dwID= GID_ROLLOVER;
config[4].dwWant= GC_ROLLOVER;

SetGestureConfig( hwnd, 0, 5, config, sizeof(GESTURECONFIG) );

これで全部のメッセージとオプションが有効になるはずです。

座標は ptsLocation に、追加パラメータは ullArguments に入ります。
GID_PAN + GF_INERTIA フラグが ON の場合は、ullArguments の上位 32bit に
慣性のパラメータが格納されているようです。
GID_INERTIA というのは存在していません。

なお、これらの動作を確認するには

・Windows7 Beta
・Windows SDK for Windows 7 BETA
・MultiTouch 対応 PC

が必要です。Vista では起動時に DLL の互換性が無くエラーになります。
HP の TouchSmart PC IQ800 を使いました。

Windows7 SDK の設定には少々注意が必要です。
新しい DirectX SDK もリリースされているので、DirectX SDK March 2009 を
併用する場合は特に。基本的にあとからリリースされた方を上にします。

・DirectX SDK March 2009
・Windows SDK for Windows 7 BETA
・VisualStudio 2008

VisualStudio の Tools → Options → Projects and Solutions → VC++ DIrectories の設定

Include files

$(DXSDK_DIR)include
$(WindowsSdkDir)\include
$(VCInstallDir)include
~

Library files (x64)

$(DXSDK_DIR)lib\x64
$(WindowsSdkDir)lib\x64
$(VCInstallDir)lib\amd64
~

Library files (x86)

$(DXSDK_DIR)lib\x86
$(WindowsSdkDir)lib
$(VCInstallDir)lib
~

またあらかじめスタートメニュー Microsoft Windows SDK v7.0 から
Windows SDK Configuration Tool を起動して v7.0 を選択しておきます。


関連エントリ
DirectX SDK March 2009
Windows7 とマルチタッチ / HP TouchSmart PC IQ800
Direct2D と Direct3D10.1 の下位互換