Delphi XE4でのMultiTouch + Gesturesのサポヌト









どういうわけか、ナヌザヌが自分の指で突くこずができるアクティブモニタヌの圢でのこれらの新しいトレンドはすべお、私に気付かれずに通過したした。 3か月前にボスが2぀の郚分画面、キヌボヌドに分割できるラップトップを賌入しなかった堎合、それらに぀いおは知りたせん。MSによっお掚進されるSurfaceではなく、 ASUS 、はるかに少ないお金で比范的。

そしお、このデバむスは理由のために賌入されたした-そこからタスクが成長し、そこから圌らは埅ちたせんでした。



デゞュレ私たちは月に膚倧な数のセミナヌを開催し、それが圌らに課されおいたので、講垫たちはこの悪名高いタコず゜フトりェアの非互換性を実蚌し始めたした。



デファクトプランナヌザヌの怒りの手玙がサポヌトメヌルに泚がれ始めたした-「テヌプを2回録音したしたが、タップしたせんでした。

圓局は、これらすべおを「砎れた」ラップトップで綿密に監芖し、TKを準備したした。



そしお、その日が来たした。 LGから23むンチ  最倧 10本の指でタッチ入力をサポヌトの3番目のモニタヌがデスクトップに蚭眮され、タスクが蚭定されたした-これは3日以内に機胜するはずです



そしお、私はXE4で働いおいたす-トラブル。



0.問題分析



幞いなこずに、私は倚くの有胜な仲間Embarcadero MVPを含むに粟通しおおり、どちらの偎でタッチサポヌトにアプロヌチするかに぀いお盞談できたすが、...マルチタッチのサポヌトに関する技術蚘事ぞのリンクを読んで、XE4䜕も茝いおいない。 私が利甚できるVCLオプションは非垞に限られおいたす。



Embarcaderoカンファレンスを少し読んだ埌、いく぀かの制限はあるものの、マルチタッチがXE7でのみ利甚可胜になったこずを知りたしたただし。



問題を解決する最も簡単な方法がXE7のアップデヌトのようだず蚀った堎合さらに、apa埌に互換性コヌドをチェックするのにかかる時間、䞊叞が䜕を評䟡するかわかりたせん。



したがっお、XE4で利甚できるものを確認したす。

プラス

-圌女はゞェスチャヌゞェスチャヌを知っおいたす。

短所

-圌女はタッチに぀いおは知りたせん圌女は知っおいたすが、倖郚ハンドラヌは提䟛したせん。

-2぀の入力ポむント2本以䞊の指を持぀ゞェスチャに぀いおは知りたせん。



次に、利甚できないものを芋おみたしょう。



  1. クラスの厳密なプラむベヌトタむプセクションでもTPlatformGestureEngine内で非衚瀺になっおいるだけで、IStylusAsyncPluginぞのヒヌプぞのIRealTimeStylus3むンタヌフェむスのサポヌトを導入しおTRealTimeStylusクラスを拡匵するこずはできたせん。
  2. このメッセヌゞはTWinControl.WndProc内で凊理されたすが、本栌的なWM_TOUCHメッセヌゞハンドラは提䟛されおいたせん。




WM_TOUCH: with FTouchManager do if (GestureEngine <> nil) and (efTouchEvents in GestureEngine.Flags) then GestureEngine.Notification(Message);
      
      







コヌドからわかるように、コントロヌルはゞェスチャ認識゚ンゞンに盎接移動したす。

どうやら-ゞェスチャが認識しない順序でキャンバス䞊の5぀の写真を移動したい堎合、なぜゞェスチャが必芁ですか



もちろん、2番目のケヌスではWM_TOUCHを自分でブロックするこずができたすが、誰かがそれを凊理し始めおデヌタを取埗したので、コヌドを再ダビングするこずから開発者を救っおください。



それでは、反察偎から行きたしょう。



1.問題の声明



ただし、圓瀟の゜フトりェアは基本的に非垞に高床なExcelであり、特定のナヌザヌこの堎合は掚定者を察象ずしおいたす。 ただし、少し蚀い盎したす。゜フトりェアの機胜ずExcelの間の距離は、MsPaintずAdobe Photoshopの違いずほが同じです。

ナヌザヌは、特定のドキュメントをMsPaintの画像だけでなく、芋積もりの​​圢匏でExcelに実装するこずもできたす。 すべおのcimusは結果です。



このプロゞェクトは、WYSIWYGのむデオロギヌに埓っお開発されたもので、90のケヌスでは、通垞の玙の文曞ず同様に、ナヌザヌが䜜業するグリッドを実装する䞀皮のカスタムクラスTCustomControlからです。



これは次のようになりたすスクリヌンショットはDragDrop䜍眮の操䜜䞭に撮圱されたもので、矢印に泚意を払わないでください。写真が䞀郚のテクニカルサポヌトから砎れ、チップなどの浮動ヒントを指しおいるためです。











このコントロヌルにはスクロヌルなどの暙準的な抂念はありたせん。 もちろん、そうですが、氎平方向の移動の堎合、たたは垂盎方向の移動の堎合-シヌトの次の行ぞの遷移で、列の操䜜を操䜜したす。

暙準のスクロヌルメッセヌゞは受け付けたせん。



OSが発行する基本バヌゞョンでは、タッチスクリヌンのタップを介しおシステムによっお゚ミュレヌトされるマりスクリックむベントず、タッチを介しおシステムによっお゚ミュレヌトされるWM_MOUSEMOVEを受信できたす。



そしお必芁なもの





XE4のゞェスチャは、マルチタッチではゲスト゚ディタヌのレベルであっおも基本的にシャヌプ化されおいないが、問題を解決する必芁があるずいう事実を考えるず、倜䞭ずっず悲しくなり、朝になっお仕事を始めたした。



2.䜿甚される甚語



先ほど蚀ったように、私はこれらの新しいトレンドすべおに粟通しおいるわけではないので、蚘事では次の定矩を䜿甚したすそれらは間違っおいる可胜性がありたす。



タップ -マりスクリックの類䌌物。指でタッチスクリヌンを1回短くクリックするず発生するむベント。

タッチ たたはタッチポむントは、指がタッチスクリヌンに接觊しおいるおよびWM_TOUCHメッセヌゞが凊理されおいる状況を衚すものです。

ルヌト -ナヌザヌが指を動かした座暙のリスト動かされたホむヌルのポむント。

セッション -指がタッチスクリヌンに觊れるず開始し、ナヌザヌが指を離さずにその䞊を移動するず続行し、指を離すず終了したす。 セッション党䜓を通しお、そのルヌトが構築されたす。

ゞェスチャは、セッションルヌトず比范されるルヌトのテンプレヌトテンプレヌトです。 たずえば、ナヌザヌが指を぀たんで、巊に匕いお離したした-これは識別子sgiLeftのゞェスチャヌです。



3. WM_TOUCH凊理に぀いお



最初に決定する必芁がありたす-ハヌドりェアはマルチタッチをサポヌトしおいたすか

これを行うには、SM_DIGITIZERパラメヌタヌを指定しおGetSystemMetricsを呌び出し、NID_READYずNID_MULTI_INPUTの2぀のフラグの結果を確認したす。



倧䜓



 tData := GetSystemMetrics(SM_DIGITIZER); if tData and NID_READY <> 0 then if tData and NID_MULTI_INPUT <> 0 then ...  ,  
      
      





残念ながら、OS Windowsでマルチタッチをサポヌトするデバむスを䜿甚しおいない堎合、この蚘事の残りの郚分は、結果を確認する可胜性のない理論に過ぎたせん。 Microsoft Surface 2.0 SDKからホむヌル゚ミュレヌタヌを䜿甚しおみるこずができたすが、詊しおはいたせん。



しかし!!! デバむスがマルチタッチをサポヌトしおいる堎合は、タッチを詊みるこずができたす。 これを行うには、任意のりィンドりメむンフォヌムなどを遞択しお、次のように蚀いたす。



 RegisterTouchWindow(Handle, 0);
      
      





この関数を呌び出さないず、遞択したりィンドりはWM_TOUCHメッセヌゞを受信したせん。

UnregisterTouchWindow関数は、りィンドりがこのメッセヌゞを受信しないように「切断」するのに圹立ちたす。



メッセヌゞハンドラヌWM_TOUCHを宣蚀したす。



 procedure WmTouch(var Msg: TMessage); message WM_TOUCH;
      
      





そしお、私たちは圌が䜕を私たちに䞎えおくれるかを理解し始めたす。



そのため、このメッセヌゞのWParamパラメヌタヌには、システムが通知する手抌し車のアクティブポむントの数が含たれおいたす。 さらに、この数倀は䞋䜍2バむトにのみ栌玍されたす。これは、システムが最倧65535の入力ポむントをサポヌトする可胜性を瀺唆しおいたす。



私はそれを理解しようずしたした-私のモニタヌは最倧10本の指を保持しおいるため、うたくいきたせんでした。 ただし、これは珟代の空想科孊小説映画を芋るずチムスですが、10個すべおを突くこずができる倚くの人々ず連携するデヌタを含む仮想テヌブルを瀺しおいたすたずえば、アバタヌは同じ、たたは忘华。



よくやった、圌らは玄束をしたが、それが刀明したように、それは長い間映画なしで働いおきたが、私は垞にニュヌスに埓っおいない。 たずえば、Consumer Electronics Show 2011で発衚されたこのような46むンチデバむスは次のずおりです。











ただし、気が散るこずはありたせん。

ただし、このメッセヌゞのLParamは、GetTouchInputInfo関数を呌び出しおメッセヌゞに関する詳现情報を取埗できる䞀皮のハンドルです。

GetTouchInputInfoを呌び出した埌、この関数の2回目の呌び出しが䞍芁な堎合、MSDNはCloseTouchInputHandleを指定するこずをお勧めしたすが、これは必芁ありたせん。 コントロヌルをDefWindowProcに転送するずき、たたはSendMessage / PostMessageを介しおデヌタを送信しようずするずき、ヒヌプ䞊のデヌタのクリアは自動的に行われたす。

詳现はこちら 。



GetTouchInputInfo関数に必芁なもの



  1. 圌女はハンドル自䜓が必芁であり、それず䞀緒に䜜業したす。
  2. 圌女は、TTouchInput芁玠の配列の圢匏で専甚のバッファを必芁ずし、そこにむベントに関するすべおの情報を配眮したす。
  3. この配列のサむズ。
  4. 配列内の各芁玠のサむズ。


よくやった4番目の段萜の助けを借りお、OSの将来のバヌゞョンでTTouchInputの構造を倉曎する可胜性をすぐに決定したしたさらに面癜いこずはありたすか



非垞に倱瀌な堎合、圌女の呌び出しは次のようになりたす。



 var Count: Integer; Inputs: array of TTouchInput; begin Count := Msg.WParam and $FFFF; SetLength(Inputs, Count); if GetTouchInputInfo(Msg.LParam, Count, @Inputs[0], SizeOf(TTouchInput)) then // ... -     CloseTouchInputHandle(Msg.LParam);
      
      





以䞊です。 ここで、Inputs配列に栌玍されおいるデヌタを把握しおみたしょう。



4. TTouchInputを凊理したす



この瞬間から、楜しみが始たりたす。



TTouchInput配列のサむズは、タッチスクリヌンに接続されおいる指の数によっお異なりたす。

手抌し車指の各ポむントに察しお、システムはセッション党䜓指を觊れおから削陀するたでを通じお倉わらない䞀意のIDを生成したす。

このIDは、TTouchInput配列の各芁玠にマップされ、dwIDパラメヌタヌに栌玍されたす。



セッションずいえば

セッション、これは...さお、このようにしたしょう









写真は各指に察しお正確に10セッションを瀺し、そのルヌト各セッション内で指が䞊に移動したポむントの配列を瀺し、各セッションはただ完了しおいたせん指はタッチスクリヌンにただ接続されおいたす。



ただし、TTouchInput構造に戻りたす。

実際、この構造のタコを䜿甚した通垞の操䜜では、いく぀かのパラメヌタヌのみが必芁です。



 TOUCHINPUT = record x: Integer; //   y: Integer; //   hSource: THandle; //  ,   dwID: DWORD; //    dwFlags: DWORD; //    //       dwMask: DWORD; dwTime: DWORD; dwExtraInfo: ULONG_PTR; cxContact: DWORD; cyContact: DWORD; end;
      
      





デモアプリケヌションをすぐに始めたしょう。

新しいプロゞェクトを䜜成し、メむンフォヌムにTMemoを配眮したす。メむンフォヌムには、タコでの䜜業のログが衚瀺されたす。



フォヌムコンストラクタヌで、それをWM_TOUCHメッセヌゞの凊理に接続したす。



 procedure TdlgSimpleTouchDemo.FormCreate(Sender: TObject); begin RegisterTouchWindow(Handle, 0); end;
      
      





次に、むベントハンドラを䜜成したす。



 procedure TdlgSimpleTouchDemo.WmTouch(var Msg: TMessage); function FlagToStr(Value: DWORD): string; begin Result := ''; if Value and TOUCHEVENTF_MOVE <> 0 then Result := Result + 'move '; if Value and TOUCHEVENTF_DOWN <> 0 then Result := Result + 'down '; if Value and TOUCHEVENTF_UP <> 0 then Result := Result + 'up '; if Value and TOUCHEVENTF_INRANGE <> 0 then Result := Result + 'ingange '; if Value and TOUCHEVENTF_PRIMARY <> 0 then Result := Result + 'primary '; if Value and TOUCHEVENTF_NOCOALESCE <> 0 then Result := Result + 'nocoalesce '; if Value and TOUCHEVENTF_PEN <> 0 then Result := Result + 'pen '; if Value and TOUCHEVENTF_PALM <> 0 then Result := Result + 'palm '; Result := Trim(Result); end; var InputsCount, I: Integer; Inputs: array of TTouchInput; begin //     InputsCount := Msg.WParam and $FFFF; //     SetLength(Inputs, InputsCount); //      if GetTouchInputInfo(Msg.LParam, InputsCount, @Inputs[0], SizeOf(TTouchInput)) then begin //   (    ) CloseTouchInputHandle(Msg.LParam); //     for I := 0 to InputsCount - 1 do Memo1.Lines.Add(Format('TouchInput №: %d, ID: %d, flags: %s', [I, Inputs[I].dwID, FlagToStr(Inputs[I].dwFlags)])); end; end;
      
      





以䞊です。



同意する-䞍可胜にだけ。 あなたの目の前のすべおのデヌタ。

タッチスクリヌンを䜿甚しおこのコヌドを詊しおみおください。開発者には、各ホむヌルバヌのIDぞのバむンドに加えお、ログに衚瀺される特定のフラグセットが䞎えられたす。

ログデヌタによれば、手抌し車のセッションの開始フラグTOUCHEVENTF_DOWN、タッチスクリヌン䞊の各指の動きフラグTOUCHEVENTF_MOVE、およびセッションの終了フラグTOUCHEVENTF_UPをすぐに刀断できたす。



次のようになりたす。







迷惑行為に぀いおすぐに予玄したす。タッチスクリヌンからTOUCHEVENTF_DOWNたたはTOUCHEVENTF_UPフラグが付いたメッセヌゞがWM_TOUCHハンドラヌに届くずは限りたせん。 「ラッパヌクラス」を実装するずきは、このニュアンスを考慮する必芁がありたす。これに぀いおは以䞋で説明したす。



䟋

珟圚、アプリケヌションはPopupMenuを衚瀺しおいたす-タッチスクリヌンをクリックするず閉じたすが、TOUCHEVENTF_DOWNフラグのあるWM_TOUCHメッセヌゞは届きたせんが、TOUCHEVENTF_MOVEフラグのある次のメッセヌゞは正垞に受信されたす。

TOUCHEVENTF_MOVEむベントハンドラヌでPopupMenuを衚瀺する堎合も同様です。

この堎合、セッションは䞭断され、TOUCHEVENTF_UPフラグが蚭定されたWM_TOUCHメッセヌゞは予期されたせん。



この動䜜は、Windows 732/64ビットでも芋られたす。Windows8以降では、䜕かが倉曎されたしたが、今では確認する機䌚がありたせん怠lazが2番目です。



ただし、「どのように機胜するか」ずいうアむデアを埗たので、もっず面癜いこずを曞きたす。



この䟋の゜ヌスコヌドは、゜ヌスアヌカむブの「 。\ Demos \ simple \ 」フォルダヌにありたす。



5.実際にマルチタッチを適甚したす。



私のモニタヌには同時に10本の指があり、ピアノを゚ミュレヌトするアプリケヌションを䜜成するこずもできたすただし、ピアノにはペダルず抌圧力に察する感床がありたすが、なぜ耇雑なものからすぐに行くのでしょうか

私に起こった最も簡単なこずは、フォヌムのキャンバス䞊の10個の正方圢であり、手抌し車の助けを借りおすべおの方向に移動できたす。

これは、最も文字通りの意味でマルチタッチを「感じる」のに十分です。



新しいプロゞェクトを䜜成したす。



各正方圢は、そのような構造ずしお説明されたす。



 type TData = record Color: TColor; ARect, StartRect: TRect; StartPoint: TPoint; Touched: Boolean; TouchID: Integer; end;
      
      





実際、この構造の最も重芁なフィヌルドはTouchIDであり、それ以倖はすべおセカンダリです。



各正方圢のデヌタをどこかに保存する必芁があるので、そのような配列ずしお宣蚀したしょう。



 FData: array [0..9] of TData;
      
      





さお、初期化をしたしょう



 procedure TdlgMultiTouchDemo.FormCreate(Sender: TObject); var I: Integer; begin DoubleBuffered := True; RegisterTouchWindow(Handle, 0); Randomize; for I := 0 to 9 do begin FData[I].Color := Random($FFFFFF); FData[I].ARect.Left := Random(ClientWidth - 100); FData[I].ARect.Top := Random(ClientHeight - 100); FData[I].ARect.Right := FData[I].ARect.Left + 100; FData[I].ARect.Bottom := FData[I].ARect.Top + 100; end; end;
      
      





たた、フォヌムのキャンバス䞊でのレンダリング今のずころ、FormPaintハンドラヌを分析せずに、少し䞋に移動したす



 procedure TdlgMultiTouchDemo.FormPaint(Sender: TObject); var I: Integer; begin Canvas.Brush.Color := Color; Canvas.FillRect(ClientRect); for I := 0 to 9 do begin Canvas.Pen.Color := FData[I].Color xor $FFFFFF; if FData[I].Touched then Canvas.Pen.Width := 4 else Canvas.Pen.Width := 1; Canvas.Brush.Color := FData[I].Color; Canvas.Rectangle(FData[I].ARect); end; end;
      
      





実行するず、次のようになりたす。











ボディキットの準備ができたした。次に、WM_TOUCHの凊理を通じお画像を倉曎しおみたしょう。



ハンドラヌで必芁なのは、ナヌザヌがクリックした正方圢のむンデックスを取埗するこずです。 しかし、最初に、手抌し車の各ポむントからの座暙をりィンドりの座暙に倉換したす。



 pt.X := TOUCH_COORD_TO_PIXEL(Inputs[I].x); pt.Y := TOUCH_COORD_TO_PIXEL(Inputs[I].y); pt := ScreenToClient(pt);
      
      





有効な座暙を手に入れたら、PtInRectを呌び出すこずで、配列内の正方圢のむンデックスを芋぀けるこずができたす。



 function GetIndexAtPoint(pt: TPoint): Integer; var I: Integer; begin Result := -1; for I := 0 to 9 do if PtInRect(FData[I].ARect, pt) then begin Result := I; Break; end; end;
      
      





ナヌザヌが指でタッチスクリヌンに觊れたずき各ポむントに固有のIDがある堎合、芋぀かった四角圢にこのIDを割り圓おたす。 これは将来圹に立぀でしょう



 if Inputs[I].dwFlags and TOUCHEVENTF_DOWN <> 0 then begin Index := GetIndexAtPoint(pt); if Index < 0 then Continue; FData[Index].Touched := True; FData[Index].TouchID := Inputs[I].dwID; FData[Index].StartRect := FData[Index].ARect; FData[Index].StartPoint := pt; Continue; end;
      
      





これは、たずえば、オブゞェクトの初期化ず手抌し車のセッションの開始です。



次のメッセヌゞは、TOUCHEVENTF_MOVEフラグが蚭定されたWM_TOUCHである可胜性がありたす。



ニュアンスがありたす

最初のケヌスでは、座暙で正方圢を怜玢したしたが、フォヌム䞊の正方圢の䜍眮が亀差する可胜性があるため、正方圢は間違いになりたす。

したがっお、MOVEの堎合、TouchIDパラメヌタヌで蚭定されたホむヌルのIDで正方圢を怜玢したす。



 function GetIndexFromID(ID: Integer): Integer; var I: Integer; begin Result := -1; for I := 0 to 9 do if FData[I].TouchID = ID then begin Result := I; Break; end; end;
      
      





必芁な正方圢を芋぀けたら、タッチセッションの開始時に指定された構造に焊点を圓おお移動したす。



 R := FData[Index].StartRect; OffsetRect(R, pt.X - FData[Index].StartPoint.X, pt.Y - FData[Index].StartPoint.Y); FData[Index].ARect := R;
      
      







さお、そしおTOUCHEVENTF_UPフラグを凊理する圢で終わる



 if Inputs[I].dwFlags and TOUCHEVENTF_UP <> 0 then begin FData[Index].Touched := False; FData[Index].TouchID := -1; Continue; end;
      
      





正方圢をタッチセッションから切断し、キャンバス自䜓を再描画したす。



非垞に単玔な䟋ですが、これは機胜し、お金を芁求したせん。

実行しおテスト-それは非垞に面癜いこずが刀明したした







矎しくするために、TData構造䜓のTouchedパラメヌタヌはFormPaint内で䜿甚され、移動された正方圢の呚囲に倪字のフレヌムを衚瀺したす。



この䟋の゜ヌスコヌドは、゜ヌスアヌカむブの「 。\ Demos \ multutouch \ 」フォルダにありたす。



6.ゞェスチャゞェスチャを理解する



マルチタッチは最初のステップに過ぎたせん。マルチタッチゞェスチャを䜿甚したいのですが...

たず、1本のタッチセッション1本の指に基づいおVCLでゞェスチャ認識がどのように実装されるかを芋おみたしょう。



TGestureEngineクラスはこれに責任があり、原則ずしおIsGesture関数のコヌドのみが必芁です。



もっず詳しく考えおみたしょう。



これは正確に2぀の郚分に分かれおおり、最初の郚分はルヌプ内の暙準ゞェスチャヌをチェックしたす。



 // Process standard gestures if gtStandard in GestureTypes then
      
      





2番目-ナヌザヌが送信したカスタムゞェスチャ



 // Process custom gestures if CustomGestureTypes * GestureTypes = CustomGestureTypes then
      
      





定矩䞊、カスタムナヌザヌゞェスチャは必芁ないため、関数の最初の郚分のみを考慮したす。

その䞻なアむデアは、FindStandardGestureを呌び出し、Recognizer.Matchを䜿甚しお枡されたルヌトず比范するこずにより、ゞェスチャヌ蚘述子を怜玢するこずです。



実際、IsGestureに送られるその他のパラメヌタヌはすべお陀倖できたす。これらはボディキットの機胜です。



秘trickは、RecognizerはIGestureRecognizerむンタヌフェむスではなく、VCLラッパヌであるずいうこずです。

これが必芁なものです。



しかし、デモの䜜成に移る前に、ゞェスチャヌ自䜓が䜕であるかを理解する必芁がありたすGerture



これはフォヌムの構造です



 TStandardGestureData = record Points: TGesturePointArray; GestureID: TGestureID; Options: TGestureOptions; Deviation: Integer; ErrorMargin: Integer; end;
      
      





ポむントは、ナヌザヌのタッチセッションからの同様のルヌトず比范されるゞェスチャヌルヌトです。

GestureIDは䞀意のゞェスチャヌ識別子です。



XE4では、Vcl.Controlsモゞュヌルにリストされおいたす。
 const // Standard gesture id's sgiNoGesture = 0; sgiLeft = 1; sgiRight = 2; ...
      
      







オプション -この堎合、それらには興味がありたせん。



DeviationずErrorMarginは、倀を瀺すパラメヌタヌです。たずえば、ゞェスチャヌ䞭の指の「振戊」です。 Y軞䞊の䜍眮を倉曎せずに、X軞に沿っお完党に平らな線を巊に描画できる可胜性は䜎いため、DeviationずErrorMarginは、ポむントの移動が有効な境界を瀺したす。



暙準のゞェスチャヌパラメヌタヌの宣蚀は、Vcl.Touch.Gesturesモゞュヌルにありたす。
 { Standard gesture definitions } const PDefaultLeft: array[0..1] of TPoint = ((X:200; Y:0), (X:0; Y:0)); CDefaultLeft: TStandardGestureData = ( GestureID: sgiLeft; Options: [goUniDirectional]; Deviation: 30; ErrorMargin: 20); PDefaultRight: array[0..1] of TPoint = ((X:0; Y:0), (X:200; Y:0)); CDefaultRight: TStandardGestureData = ( GestureID: sgiRight; Options: [goUniDirectional]; Deviation: 30; ErrorMargin: 20); PDefaultUp: array[0..1] of TPoint = ((X:0; Y:200), (X:0; Y:0)); CDefaultUp: TStandardGestureData = ( GestureID: sgiUp; Options: [goUniDirectional]; Deviation: 30; ErrorMargin: 20); ...
      
      







したがっお、ゞェスチャの圢匏を知っおいるず、ルヌトポむントに入力しお䞀意のIDを蚭定するこずで、実行時に独自のバヌゞョンのゞェスチャを個別に準備できたす。

ただし、今は必芁ありたせん。 暙準のゞェスチャヌに基づいお䜕ができるかを芋おみたしょう。



Recognizerが認識したゞェスチャのIDを返すこずで、最も簡単な䟋を䜜成しおいたす。ここでは、ナヌザヌがタッチスクリヌンから入力するルヌトに技術的に類䌌した4぀のポむントの配列を䜜成したす。



たずえば、次のように
 program recognizer_demo; {$APPTYPE CONSOLE} {$R *.res} uses Windows, Vcl.Controls, SysUtils, TypInfo, Vcl.Touch.Gestures; type TPointArray = array of TPoint; function GetGestureID(Value: TPointArray): Byte; var Recognizer: TGestureRecognizer; GestureID: Integer; Data: TStandardGestureData; Weight, TempWeight: Single; begin Weight := 0; Result := sgiNone; Recognizer := TGestureRecognizer.Create; try for GestureID := sgiLeft to sgiDown do begin FindStandardGesture(GestureID, Data); TempWeight := Recognizer.Match(Value, Data.Points, Data.Options, GestureID, Data.Deviation, Data.ErrorMargin); if TempWeight > Weight then begin Weight := TempWeight; Result := GestureID; end; end; finally Recognizer.Free; end; end; const gesture_id: array [sgiNone..sgiDown] of string = ( 'sgiNone', 'sgiLeft', 'sgiRight', 'sgiUp', 'sgiDown' ); var I: Integer; Data: TPointArray; begin SetLength(Data, 11); //     for I := 0 to 10 do begin Data[I].X := I * 10; Data[I].Y := 0; end; Writeln(gesture_id[GetGestureID(Data)]); //     for I := 0 to 10 do begin Data[I].X := 500 - I * 10; Data[I].Y := 0; end; Writeln(gesture_id[GetGestureID(Data)]); //     for I := 0 to 10 do begin Data[I].X := 0; Data[I].Y := 500 - I * 10; end; Writeln(gesture_id[GetGestureID(Data)]); //     for I := 0 to 10 do begin Data[I].X := 0; Data[I].Y := I * 10; end; Writeln(gesture_id[GetGestureID(Data)]); Readln; end.
      
      







開始埌、次の図が衚瀺されたす。











予想通り。

この䟋の゜ヌスコヌドは、゜ヌスを含むアヌカむブ内のフォルダヌ「 。\ Demos \認識機胜 」にありたす。



そしお今...



7.マルチタッチゞェスチャゞェスチャを認識する



この章では、この蚘事の䞻なアむデアに぀いお説明したす。このように蚀えば、このテキストがすべお茉っおいるチップです。

今-技術的な詳现はなく、アプロヌチ自䜓のみ



したがっお、珟圚利甚可胜なものは次のずおりです。

  1. 各タッチセッションからデヌタを取埗する方法を知っおいたす。
  2. 各タッチセッションのゞェスチャを認識できたす。


䟋

  1. ナヌザヌはタッチスクリヌンをクリックしお巊にスワむプしたした。
  2. ON_TOUCH + TOUCHEVENTF_DOWNハンドラヌでセッションの開始を蚘録し、TOUCHEVENTF_MOVEの到着時にすべおのりェむポむントを蚘録し、TOUCHEVENTF_UPを受け取った瞬間に、以前に蚘録したポむント配列をGetGestureID関数に枡したした。
  3. 結果を導き出したした。


しかし、ナヌザヌが䞀床に2本の指で同じこずをしたず想像しおください。

  1. 各指に぀いお、独自のセッションを開始したす。
  2. 圌女のルヌトを曞きたす。
  3. 各セッションの終わりに、ゞェスチャ認識に転送したす。


同じりィンドりで実行された2぀のセッションのゞェスチャIDが䞀臎する堎合たずえば、sgiLeft、結論を出すこずができたす-2本の指で巊にスワむプしたした。



しかし、セッションルヌト䞊のすべおのポむントに同じ座暙が含たれおいる堎合はどうでしょうか

その埌、ゞェスチャヌはなく、いわゆるタップ1本たたは耇数の指が発生したした。

さらに、PopupMenuが通垞衚瀺される「プレスアンドタップ」ゞェスチャもこの条件に該圓したす。



したがっお、問題の䞻な定匏化を考慮しお、必芁なすべおのゞェスチャを1本、2本、3本の指で制埡できたすただし、少なくずも10本すべお。



たた、2぀のセッションのゞェスチャヌが䞀臎しなかった堎合はどうなりたすか

それらを分析するには、これは問題の珟圚のステヌトメントには含たれおいたせんが、最初のセッションのsgiLeftゞェスチャヌず2番目のセッションのsgiRightゞェスチャヌをズヌムずしお解釈できるず蚀っおも安党です。2回のタッチセッションのみに基づくsgiSemiCircleLeftたたはsgiSemiCircleRightゞェスチャに基づいお、Rotateでさえ怜出するこずができたす。



貫通



この方法で簡単に゚ミュレヌトできるゞェスチャの既定のリストを次に瀺したす

。Windowsタッチゞェスチャの抂芁



残念ながら、䜕らかの理由で、これらはすべおXE4に実装されおおらず、第7バヌゞョンからのみ利甚可胜になりたした完党に確実かどうかはわかりたせん。



8.゚ンゞンの技術蚈画



理論郚分が終了したら、今床はこれらすべおを実践し、開発者が盎面しおいるいく぀かの問題をすぐに怜蚎したす。



問題の回数

通垞、アプリケヌションには数癟のりィンドりがありたす。ほずんどの堎合、システムはWM_LBUTTONCLICKプランメッセヌゞなどを生成し、りィンドりの通垞の動䜜ボタン、線集、スクロヌルなどには十分ですが、同じSysListView32には十分ですWM_SCROLLメッセヌゞが生成されないため、2本の指でのゞェスチャヌによるスクロヌルは発生したせん。しかし、カスタムコントロヌルもありたす。

各りィンドりのりィンドりプロシヌゞャを拡匵するのは非垞に手間がかかるため、䜕らかの方法で決定する必芁がありたす。どのりィンドりがマルチタッチをサポヌトする必芁があり、これを最も普遍的に行う必芁がありたす。

それは次のずおりです。りィンドりが登録され、マルチタッチのすべおの䜜業を担圓する特定のマルチタッチマネヌゞャヌが必芁です。



問題点2

TWinControlの各むンスタンスを曞き換えずに少し普遍的に蚘述しおいるため、RecreateWndは通垞のVCLメカニズムの1぀を呌び出すため、りィンドりの再䜜成を䜕らかの方法で远跡する必芁がありたす。これを行わないず、最初にりィンドりを再䜜成するずきに、以前に登録したTWinControlがWM_TOUCHメッセヌゞの受信を停止するため、マネヌゞャヌのすべおの䜜業が平準化されたす。



問題番号3

マネヌゞャヌは、タッチセッションに関するすべおのデヌタを保存し、セッションの開始ず終了の䞭断の状況を凊理できる必芁がありたすダりンフラグずアップフラグの通知が垞に送信されるずは限らないため。たた、セッションの長さが非垞に長くなる可胜性があるこずに泚意しおくださいセッションのルヌトのすべおのポむントを保存する堎合のメモリ消費。



たた、マルチタッチマネヌゞャヌが異なるりィンドり内のゞェスチャを区別できるようにしたいず思いたす。

たずえば、ナヌザヌが巊のりィンドりに2本の指を眮き、右に2本の指を眮いお4぀のマルチタッチセッション、䞭倮に指を接続するず、通知は右に2本指のゞェスチャヌを、巊に2本指のゞェスチャヌを受け取るはずです。











しかし、残念ながら、これは機胜したせん。WM_TOUCHメッセヌゞは、セッションが開始されたりィンドりにのみ届き、残りのりィンドりは無芖されたす。



9.マルチタッチ゚ンゞンのベヌスフレヌムを構築する



最初に、クラス実装のニュアンスを決定したしょう。

技術的には、倖郚プログラマヌの芳点から最も䟿利なのは、すべおの䜜業を匕き受け、最終むベントの呌び出しを陀いお開発者に通知するナニバヌサル゚ンゞンの実装です。



この堎合、開発者は必芁なりィンドりを䞀床゚ンゞンに登録し、そこからのゞェスチャヌを分析しお特定のりィンドりに向けお必芁なものを凊理するだけです。たずえば、2本指のゞェスチャヌで同じスクロヌルを゚ミュレヌトしたす。



゚ンゞン自䜓はシングルトンずしお実装されたす。

第䞀に、垞に同じこずを行うクラスむンスタンスを䜜成しおも意味がありたせん。これは、デヌタストレヌゞ甚に匷化されたTStringListではなく、すべおのプロゞェクトりィンドりに察しお単䞀の䜜業ロゞックを実装する゚ンゞンです。

次に、゚ンゞン自䜓の実装にはわずかなニュアンスがありたすそれに぀いおは少し埌で。そのため、シングルトン圢匏の実装が最も簡単になりたす。そうでない堎合は、クラスのロゞックを根本的に再耇雑化する必芁がありたす。



したがっお、゚ンゞンは以䞋を提䟛する必芁がありたす。

  1. りィンドりを登録し、登録からりィンドりを削陀する方法
  2. 開発者がハンドラヌを実装する必芁がある倖郚むベントのセット。


倖郚むベントは次のようなものです



。OnBeginTouch-このむベントは、WM_TOUCHメッセヌゞを受信したずきに発生したす。



説明させおください4番目の章では、次のコヌドが䞎えられたした。



 //     InputsCount := Msg.WParam and $FFFF;
      
      





぀たり手抌し車のいく぀かの実際のポむントがありたす。

開発者に譊告するのは、その数に぀いおです。



OnTouch-このむベントでは、各TTouchInput構造に含たれるデヌタを開発者に通知したすが、もう少しコヌミングしたす。 ポむントに関するデヌタをりィンドり座暙に倉換し、正しいフラグを蚭定するなど、なぜ開発者に冗長な情報をロヌドし、冗長なコヌドを曞かせるのですか



OnEndTouch-これにより、WM_TOUCHメッセヌゞ凊理サむクルが完了したず衚瀺されたす。



OnGecture-ゞェスチャが認識されたず゚ンゞンが刀断するず、開発者はこのメッセヌゞを受け取りたす。



クラスはシングルトンずしお実装され、その䞭に耇数のりィンドりが登録されるため、4぀のむベントすべおをクラスプロパティずしお宣蚀するこずはできたせん。



正確に蚀うず、それはもちろん可胜ですが、2番目に登録されたりィンドりはすぐにむベントハンドラヌを自分自身に再割り圓おし、最初のりィンドりは静かに吞わなければなりたせん。

したがっお、登録されたりィンドりのリストに加えお、それらに割り圓おられた゚ンゞンむベントハンドラを保持する必芁がありたす。



ただし、ここですべおを実行しようずしたす。

新しいプロゞェクトを䜜成し、SimpleMultiTouchEngineなどの名前の新しいモゞュヌルを远加したす。



最初に、WM_TOUCHを凊理するずきに関心のあるフラグを宣蚀したす。



 type TTouchFlag = ( tfMove, //   tfDown, //    tfUp //     ); TTouchFlags = set of TTouchFlag;
      
      





各ポむントに぀いお倖郚開発者に枡す構造を説明したす。



 TTouchData = record Index: Integer; //      TTouchInput ID: DWORD; //  ID  Position: TPoint; //     Flags: TTouchFlags; //  end;
      
      





OnTouchBeginむベントの宣蚀は次のようになりたす。



 TTouchBeginEvent = procedure(Sender: TObject; nCount: Integer) of object;
      
      





そしお、これがOnTouchの倖芳です。



 TTouchEvent = procedure(Sender: TObject; Control: TWinControl; TouchData: TTouchData) of object;
      
      





OnEndTouchの堎合、通垞のTNotifyEventで十分です。



登録された各りィンドりに割り圓おられた割り圓おられたむベントハンドラのデヌタは、次の構造に栌玍されたす。



 TTouchHandlers = record BeginTouch: TTouchBeginEvent; Touch: TTouchEvent; EndTouch: TNotifyEvent; end;
      
      





新しいクラスを宣蚀したす



  TSimleMultiTouchEngine = class private const MaxFingerCount = 10; private type TWindowData = record Control: TWinControl; Handlers: TTouchHandlers; end; private FWindows: TList<TWindowData>; FMultiTouchPresent: Boolean; protected procedure DoBeginTouch(Value: TTouchBeginEvent; nCount: Integer); virtual; procedure DoTouch(Control: TWinControl; Value: TTouchEvent; TouchData: TTouchData); virtual; procedure DoEndTouch(Value: TNotifyEvent); virtual; protected procedure HandleTouch(Index: Integer; Msg: PMsg); procedure HandleMessage(Msg: PMsg); public constructor Create; destructor Destroy; override; procedure RegisterWindow(Value: TWinControl; Handlers: TTouchHandlers); procedure UnRegisterWindow(Value: TWinControl); end;
      
      





順番に



MaxFingerCount定数には、クラスで䜿甚できる手抌し車の最倧数が含たれおいたす。



構造TWindowData-登録されたりィンドりず、プログラマヌが割り圓おたハンドラヌのリストが含たれたす。



フィヌルドFWindowsTList-登録されたりィンドりずハンドラヌのリストで、そこからクラスでの䜜業を通しお螊りたす。



FMultiTouchPresentフィヌルドは、クラスのコンストラクタヌで初期化されるフラグです。

ハヌドりェアがマルチタッチを保持しおいる堎合はTrueが含たれたす。このフラグに基づいお、クラスのロゞックの䞀郚が無効になりたすただ実行できないのに、なぜ远加のゞェスチャヌを行うのですか。



最初の保護されたセクション-䟿宜䞊、倖郚むベントぞのすべおの呌び出しが行われたす。



HandleTouchプロシヌゞャぱンゞンのコア゚ンゞンであり、WM_TOUCHメッセヌゞの凊理を担圓するのは圌女です。



HandleMessageプロシヌゞャは補助です。そのタスクは、登録されおいるりィンドりのどれにメッセヌゞが送信されたかを刀別し、HandleTouchを呌び出しお、芋぀かったりィンドりのむンデックスを枡すこずです。



パブリックセクション-コンストラクタ、デストラクタ、りィンドりの登録および登録解陀。



クラスの実装を進める前に、すぐにシングルトンキットを䜜成したす。
  function MultiTouchEngine: TSimleMultiTouchEngine; implementation var _MultiTouchEngine: TSimleMultiTouchEngine = nil; function MultiTouchEngine: TSimleMultiTouchEngine; begin if _MultiTouchEngine = nil then _MultiTouchEngine := TSimleMultiTouchEngine.Create; Result := _MultiTouchEngine; end; ... initialization finalization _MultiTouchEngine.Free; end.
      
      







そしお、すべおの最埌に、゚ンゞンに登録されおいるりィンドりに送信されたWM_TOUCHメッセヌゞを受信するコヌルバックトラップ。
 var FHook: HHOOK = 0; function GetMsgProc(nCode: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall; begin if (nCode = HC_ACTION) and (WParam = PM_REMOVE) then if PMsg(LParam)^.message = WM_TOUCH then MultiTouchEngine.HandleMessage(PMsg(LParam)); Result := CallNextHookEx(FHook, nCode, WParam, LParam); end;
      
      







念のため、䜿甚されるモゞュヌルのリストは次のようになりたす。
 uses Windows, Messages, Classes, Controls, Generics.Defaults, Generics.Collections, Vcl.Touch.Gestures;
      
      







それでは、゚ンゞン自䜓の実装に぀いお芋おいきたしょう。コンストラクタヌから始めたしょう。



 constructor TSimleMultiTouchEngine.Create; var Data: Integer; begin // ,     Data := GetSystemMetrics(SM_DIGITIZER); FMultiTouchPresent := (Data and NID_READY <> 0) and (Data and NID_MULTI_INPUT <> 0); //  ,       if not FMultiTouchPresent then Exit; //         FWindows := TList<TWindowData>.Create( //   IndexOf          Control //    TComparer<twindowdata>.Construct( function (const A, B: TWindowData): Integer begin Result := Integer(A.Control) - Integer(B.Control); end) ); end;
      
      





食り気のないかなりシンプルなデザむナヌ、コメントですべおのステップを芋るこずができたす。

ただし、デストラクタも簡単です。



 destructor TSimleMultiTouchEngine.Destroy; begin if FHook <> 0 then UnhookWindowsHookEx(FHook); FWindows.Free; inherited; end;
      
      





デストラクタの唯䞀のニュアンスは、トラップが以前にむンストヌルされおいた堎合、その陀去です。

次に、開発者が倖郚から利甚できる2぀の固有のパブリックプロシヌゞャの実装に移りたしょう。



゚ンゞンでのりィンドり登録



 procedure TSimleMultiTouchEngine.RegisterWindow(Value: TWinControl; Handlers: TTouchHandlers); var WindowData: TWindowData; begin //     -  if not FMultiTouchPresent then Exit; //    IndexOf ,     WindowData.Control := Value; //      , //     if FWindows.IndexOf(WindowData) < 0 then begin //    WindowData.Handlers := Handlers; //     RegisterTouchWindow(Value.Handle, 0); //       FWindows.Add(WindowData); end; //      if FHook = 0 then FHook := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgProc, HInstance, GetCurrentThreadId); end;
      
      





ただし、IndexOfを呌び出す堎合の唯䞀のニュアンスはすべおコメントされたす。 CompareMemが2぀の構造を盞互に比范するのではなく、構造の1぀のフィヌルドControlのみに察しお機胜するように、TComparerはリストクラスのコンストラクタに実装されたした。



コヌドからわかるように、ロゞックは単玔です。りィンドりを䞀般リストに远加するず、クラスはWH_GETMESSAGEトラップを開始し以前に実行されおいない堎合、珟圚のスレッド内でのみ機胜したす。



倉数FMultiTouchPresentに぀いおは別に説明したす。

コヌドからわかるように、それは単にヒュヌズずしお機胜し、有甚なこずができない堎合はクラスのロゞック党䜓を無効にしたす。

削陀するず、ハヌドりェアにタッチスクリヌンがたったくない堎合のトラップの蚭定により、アプリケヌションの各りィンドりのメッセヌゞ取埗サむクルにわずかな「オヌバヌヘッド」が発生したす。必芁ですか



登録からりィンドりを削陀するこずも同じ原理に埓い、りィンドりがもうない堎合はトラップがオフになりたす。



 procedure TSimleMultiTouchEngine.UnRegisterWindow(Value: TWinControl); var Index: Integer; WindowData: TWindowData; begin //     -  if not FMultiTouchPresent then Exit; //    IndexOf ,     WindowData.Control := Value; //   Index := FWindows.IndexOf(WindowData); if Index >= 0 then //  ,     FWindows.Delete(Index); //    ,       if FWindows.Count = 0 then begin //   UnhookWindowsHookEx(FHook); FHook := 0; end; end;
      
      





実際、゚ンゞンのロゞック党䜓は単玔です。登録のりィンドりを受け入れ、WM_TOUCHメッセヌゞを受信するず、クラスのシングルトンにアクセスしおHandleMessageプロシヌゞャを呌び出すトラップを起動したした。



 procedure TSimleMultiTouchEngine.HandleMessage(Msg: PMsg); var I: Integer; begin for I := 0 to FWindows.Count - 1 do //   ,    if FWindows[I].Control.Handle = Msg^.hwnd then begin //      HandleTouch(I, Msg); Break; end; end;
      
      





そしおここに、䜜業の論理党䜓が展開するクラスの䞭心的な手順がありたす。



 procedure TSimleMultiTouchEngine.HandleTouch(Index: Integer; Msg: PMsg); var TouchData: TTouchData; I, InputsCount: Integer; Inputs: array of TTouchInput; Flags: DWORD; begin // ,      InputsCount := Msg^.wParam and $FFFF; if InputsCount = 0 then Exit; //          if InputsCount > MaxFingerCount then InputsCount := MaxFingerCount; //       SetLength(Inputs, InputsCount); if not GetTouchInputInfo(Msg^.LParam, InputsCount, @Inputs[0], SizeOf(TTouchInput)) then Exit; CloseTouchInputHandle(Msg^.LParam); //       //       DoBeginTouch(FWindows[Index].Handlers.BeginTouch, InputsCount); for I := 0 to InputsCount - 1 do begin TouchData.Index := I; //      ID   //        ( Down  Up) //       TouchData.ID := Inputs[I].dwID; //        TouchData.Position.X := TOUCH_COORD_TO_PIXEL(Inputs[I].x); TouchData.Position.Y := TOUCH_COORD_TO_PIXEL(Inputs[I].y); TouchData.Position := FWindows[Index].Control.ScreenToClient(TouchData.Position); //    TouchData.Flags := []; Flags := Inputs[I].dwFlags; if Flags and TOUCHEVENTF_MOVE <> 0 then Include(TouchData.Flags, tfMove); if Flags and TOUCHEVENTF_DOWN <> 0 then Include(TouchData.Flags, tfDown); if Flags and TOUCHEVENTF_UP <> 0 then Include(TouchData.Flags, tfUp); //           DoTouch(FWindows[Index].Control, FWindows[Index].Handlers.Touch, TouchData); end; //       //       DoEndTouch(FWindows[Index].Handlers.EndTouch); end;
      
      





これに぀いおは蚘事の第5章ですでに説明しおいるため、コヌドに぀いおさらに説明するこずは意味がありたせん。結果ずしお埗られるマルチタッチ゚ンゞンの操䜜に移りたしょう。゜ヌスアヌカむブ



の「。\ Demos \ multitouch_engine_demo \」フォルダヌにあるSimleMultiTouchEngine.pasモゞュヌルの゜ヌスコヌド。



10. TSimleMultiTouchEngineの䜿甚



新しいものを思い付くこずはせず、5番目の章からプロゞェクトを再珟したす。䞻な倉曎点は、TSimleMultiTouchEngineがマルチタッチサポヌトを提䟛するこずです。



第9章で䜜成したプロゞェクトで、第5章のTData構造䜓ずFData配列の宣蚀を远加し、FormPaintハンドラヌをコピヌしたす。これはすべお倉曎されたせん。



2぀のハンドラヌを宣蚀したす。



 procedure OnTouch(Sender: TObject; Control: TWinControl; TouchData: TTouchData); procedure OnTouchEnd(Sender: TObject);
      
      





䜿甚するモゞュヌルで、SimleMultiTouchEngineを接続し、クラスコンストラクタヌをわずかに倉曎したす。



 procedure TdlgMultiTouchEngineDemo.FormCreate(Sender: TObject); var I: Integer; Handlers: TTouchHandlers; begin DoubleBuffered := True; // RegisterTouchWindow(Handle, 0); Randomize; for I := 0 to 9 do begin FData[I].Color := Random($FFFFFF); FData[I].ARect.Left := Random(ClientWidth - 100); FData[I].ARect.Top := Random(ClientHeight - 100); FData[I].ARect.Right := FData[I].ARect.Left + 100; FData[I].ARect.Bottom := FData[I].ARect.Top + 100; end; ZeroMemory(@Handlers, SizeOf(TTouchHandlers)); Handlers.Touch := OnTouch; Handlers.EndTouch := OnTouchEnd; MultiTouchEngine.RegisterWindow(Self, Handlers); end;
      
      





実際、倉曎は最小限であり、RegisterTouchWindowを呌び出す代わりに、実装したばかりのMultiTouchEngineに䜜業をシフトしたす。



OnTouchEndハンドラヌは簡単です。



 procedure TdlgMultiTouchEngineDemo.OnTouchEnd(Sender: TObject); begin Repaint; end;
      
      





キャンバス党䜓を再描画するだけです。



ここで、OnTouchハンドラヌのコヌド以前はWmTouchハンドラヌに実装されおいたが䜕に倉わったかを芋おみたしょう。



 procedure TdlgMultiTouchEngineDemo.OnTouch(Sender: TObject; Control: TWinControl; TouchData: TTouchData); function GetIndexAtPoint(pt: TPoint): Integer; var I: Integer; begin Result := -1; for I := 0 to 9 do if PtInRect(FData[I].ARect, pt) then begin Result := I; Break; end; end; function GetIndexFromID(ID: Integer): Integer; var I: Integer; begin Result := -1; for I := 0 to 9 do if FData[I].TouchID = ID then begin Result := I; Break; end; end; var Index: Integer; R: TRect; begin if tfDown in TouchData.Flags then begin Index := GetIndexAtPoint(TouchData.Position); if Index < 0 then Exit; FData[Index].Touched := True; FData[Index].TouchID := TouchData.ID; FData[Index].StartRect := FData[Index].ARect; FData[Index].StartPoint := TouchData.Position; Exit; end; Index := GetIndexFromID(TouchData.ID); if Index < 0 then Exit; if tfUp in TouchData.Flags then begin FData[Index].Touched := False; FData[Index].TouchID := -1; Exit; end; if not (tfMove in TouchData.Flags) then Exit; if not FData[Index].Touched then Exit; R := FData[Index].StartRect; OffsetRect(R, TouchData.Position.X - FData[Index].StartPoint.X, TouchData.Position.Y - FData[Index].StartPoint.Y); FData[Index].ARect := R; end;
      
      







むデオロギヌはあたり倉化しおいたせんが、叀いバヌゞョンよりもはるかに読みやすくなっおいたす。

そしお最も重芁なのは、第5章のコヌドず同じように機胜するこずです。





サンプルの゜ヌスコヌドは、゜ヌスアヌカむブ内のフォルダヌ " 。\ Demos \ multitouch_engine_demo \ "にありたす。



それで、同じキムスずは䜕ですか、おそらくあなたは尋ねたす。結局のずころ、メむンフォヌムのコヌドのサむズずその操䜜のアルゎリズムは実際には倉曎されず、さらに、远加のモゞュヌルがSimleMultiTouchEngine.pasの圢匏で277行のコヌドコメント付きずしお珟れたした。

そのたたにしお、本圓に必芁な堎合にのみWM_TOUCHハンドラヌを自分で実装する方が簡単かもしれたせん。



原則ずしお、これがその方法です-この゚ンゞンは、第8章で述べた3぀の最初の問題のみを解決したすが、本圓です。



そしお、シムスは次の通りです...



11.゚ンゞンにゞェスチャヌのサポヌトを含めたす



䞊蚘で実装されたMultiTouchEngineには、蚈画された問題の残りの2぀のポむントに察する解決策はありたせん。それなしでは、プロゞェクト階局の単なる远加クラスになりたすもちろん、このクラスはすべおの苊しんでいる人にマルチタッチを提䟛できたすが、本質は倉わりたせん。



問題3からすぐに始めたしょう。



たず、ゞェスチャヌ゚ンゞンず倖郚むベントハンドラヌによっお認識される型を宣蚀したす。



 //    TGestureType = ( gtNone, //    gtTap, gt2Tap, gt3Tap, //   (1, 2, 3 ) gtLeft, gtRight, gtUp, gtDown, //      gt2Left, gt2Right, gt2Up, gt2Down, //      gt3Left, gt3Right, gt3Up, gt3Down //      ); //     TGestureEvent = procedure(Sender: TObject; Control: TWinControl; GestureType: TGestureType; Position: TPoint; Completed: Boolean) of object;
      
      





このクラスでは、15皮類のゞェスチャヌを認識できる必芁がありたすgtNoneを陀く。



TGestureEvent宣蚀のCompletedパラメヌタヌに泚意しおください。このフラグは、ゞェスチャの完了を開発者に通知したすWM_TOUCH + TOUCHEVENTF_UPメッセヌゞが到着したす。



これが行われる理由たずえば、ナヌザヌが2本の指でタッチスクリヌンをクリックしお巊に移動した堎合、原則ずしおりィンドりをスクロヌルする必芁がありたすが、ゞェスチャが完了するのを埅぀ず正しく動䜜しないため、マルチタッチ゚ンゞンは定期的に倖郚OnGestureむベントを生成し、その䞭で発生するこずができたす手抌し車のセッション䞭に必芁な右スクロヌル。開発者はこのハンドラヌで、ゞェスチャヌが完了したかどうかをCompletedパラメヌタヌによっお理解できたすたずえば、gtTapが衚瀺され、CompletedパラメヌタヌがFalseに蚭定されおいる堎合、䜕もする必芁はなく、終了を埅぀䟡倀がありたす。



セッション䞭にOnGestureむベントが生成される頻床は、10に蚭定したGesturePartSize定数によっお異なりたす。぀たり、セッションポむントの数が定数の倍数になるず陀算の剰䜙はれロになりたす、むベントが生成されたす。



各セッションのデヌタは、この配列に保存されたす



 TPointArray = array of TPoint;
      
      





さお、次のように各セッションを蚘述する構造を宣蚀したす。



  TGestureItem = record ID, // ID ,     ControlIndex: Integer; //  ,      Data: TList<TPoint>; //  ,        Done: Boolean; //      end;
      
      





おそらく、各タッチセッションのデヌタを栌玍するクラスを宣蚀するこずは匕き続き可胜です。
 // ,         //   10  TGesturesData = class ... strict private //      FData: array [0..MaxFingerCount - 1] of TGestureItem; ... public ... //   procedure StartGesture(ID, ControlIndex: Integer; Value: TPoint); //      function AddPoint(ID: Integer; Value: TPoint): Boolean; //   procedure EndGesture(ID: Integer); //         procedure ClearControlGestures(ControlIndex: Integer); //         function GetGesturePath(ID: Integer): TPointArray; //        OnEndAllGestures  OnPartComplete property LastControlIndex: Integer read FLastControlIndex; //         LastControlIndex property OnEndAllGestures: TGesturesDataEvent read FEndAll write FEndAll; //     GesturePartSize       LastControlIndex property OnPartComplete: TGesturesDataEvent read FPart write FPart; end;
      
      







これは実際には特別なフリルを含たない非垞に単玔なクラスであるため、蚘事に沿ったデモプロゞェクトの䟋ですべおを芋るこずができるため、各関数の実装に぀いおは怜蚎したせん。



圌の党仕事は

  1. StartGestureおよびAddPoint呌び出しを通じお倖郚からのデヌタを保存したす。
  2. AddPointを呌び出すたびに、リストのサむズを確認したす
     デヌタTList <TPoint> 
    ControlIndexりィンドりに関連付けられたセッションごずに、必芁に応じおOnPartCompleteを呌び出したす。
  3. EndGestureを呌び出した埌、同じControlIndexを持぀すべおのセッションを確認し、すべお完了した堎合は、OnEndAllGesturesを呌び出したす。


これぱンゞンの単なるセッションリポゞトリであり、TGestureRecognizerは保存されおいるデヌタを凊理したす。



次の2぀のフィヌルドを远加しお、基本クラスを拡匵したす。
 //         FGesturesData: TGesturesData; //        FGestureRecognizer: TGestureRecognizer;
      
      







コンストラクタヌで、セッションリポゞトリを䜜成しお初期化したす。
 FGesturesData := TGesturesData.Create; FGesturesData.OnEndAllGestures := OnEndAllGestures; FGesturesData.OnPartComplete := OnPartComplete; FGestureRecognizer := TGestureRecognizer.Create;
      
      







その埌、HandleTouchメ゜ッドに戻りたす。ここで、TouchData構造䜓のフラグを蚭定するコヌドを少し拡匵する必芁がありたす。



 TouchData.Flags := []; Flags := Inputs[I].dwFlags; if Flags and TOUCHEVENTF_MOVE <> 0 then begin Include(TouchData.Flags, tfMove); //    ,    //  ,     if not FGesturesData.AddPoint(TouchData.ID, TouchData.Position) then //      ,    FGesturesData.StartGesture(TouchData.ID, Index, TouchData.Position); end; if Flags and TOUCHEVENTF_DOWN <> 0 then begin Include(TouchData.Flags, tfDown); //      , //      ID FGesturesData.StartGesture(TouchData.ID, Index, TouchData.Position); end; if Flags and TOUCHEVENTF_UP <> 0 then begin Include(TouchData.Flags, tfUp); //         // -    . //        , //  FGesturesData        FGesturesData.EndGesture(TouchData.ID); end;
      
      





実際、これは各セッションのデヌタりェアハりスでのほがすべおのアクティブな䜜業です。



タッチセッションの完了ず郚分的な完了のむベントハンドラヌは非垞に簡単です。
 // //             //  Values  ID  , //         // ============================================================================= procedure TTouchManager.OnPartComplete(Values: TBytes); var Position: TPoint; GestureType: TGestureType; begin //       ? GestureType := RecognizeGestures(Values, Position); //   ,     if GestureType <> gtNone then DoGesture( FWindows[FGesturesData.LastControlIndex].Control, FWindows[FGesturesData.LastControlIndex].Handlers.Gesture, GestureType, Position, //            False); end;
      
      





, :



 // //           //  Values  ID    // ============================================================================= procedure TTouchManager.OnEndAllGestures(Values: TBytes); var Position: TPoint; GestureType: TGestureType; begin try //       ? GestureType := RecognizeGestures(Values, Position); //   ,     if GestureType <> gtNone then DoGesture( FWindows[FGesturesData.LastControlIndex].Control, FWindows[FGesturesData.LastControlIndex].Handlers.Gesture, GestureType, Position, //       True); finally //        FGesturesData.ClearControlGestures(FGesturesData.LastControlIndex); end; end;
      
      





, , , .



ハンドラヌコヌドからわかるように、すべおの䞻芁な䜜業はRecognizeGestures関数で行われたす。そのロゞックに぀いおは、第7章で既に説明したした。



次のようになりたす。



 // //          TGesturesData //  Values  ID  , //         // ============================================================================= function TTouchManager.RecognizeGestures(Values: TBytes; var Position: TPoint): TGestureType; var I, A, ValueLen, GestureLen: Integer; GestureID: Byte; GesturePath: TPointArray; NoMove: Boolean; begin Result := gtNone; //   ,      ValueLen := Length(Values); //     (    ),   if ValueLen > 3 then Exit; //   : //    ID     ( GetGestureID), //      sgiLeft //       ,      ID    , //    -        //          //          //         , //            GestureID := sgiNoGesture; NoMove := True; for I := 0 to ValueLen - 1 do begin // ,       TPoint GesturePath := FGesturesData.GetGesturePath(Values[I]); GestureLen := Length(GesturePath); //      - ,       if GestureLen = 0 then Exit; //       if NoMove then for A := 1 to GestureLen - 1 do if GesturePath[0] <> GesturePath[A] then begin NoMove := False; Break; end; //   . //     .      ,   //     ,           Position := GesturePath[GestureLen - 1]; //  ID     if I = 0 then GestureID := GetGestureID(GesturePath) else //   ID     ,    if GestureID <> GetGestureID(GesturePath) then Exit; end; //     ID         if (GestureID = sgiNoGesture) then begin if NoMove then case ValueLen of 1: Result := gtTap; 2: Result := gt2Tap; 3: Result := gt3Tap; end; end else begin Dec(ValueLen); Result := TGestureType(3 + GestureID + ValueLen * 4); end; end;
      
      





この関数には、補助的なGetGestureIDが必芁です。これに぀いおは、第6章で既に説明したした。



これらすべおの操䜜の埌、第8章で衚明された問題番号3は解決されたず蚀えたす。぀たり、各セッションに関するデヌタを保存でき、さらに、どのりィンドりで保持されおいるかを把握できたす。



問題は2぀目です。



12.りィンドりの再䜜成を怜出したす



先ほど蚀ったように、RecreateWndの呌び出しは基本的に通垞のVCLメカニズムです。

ただし、゚ンゞンのロゞック党䜓を倧きく損なう可胜性がありたす。りィンドりを再䜜成するず、これたでのずころ、新しく䜜成されたハンドルでRegisterTouchWindowを2回目に呌び出す人はいたせん。したがっお、りィンドりが゚ンゞンに登録され続けたずしおも、WM_TOUCHメッセヌゞは送信されなくなりたす。



このタスクにアプロヌチする方法はいく぀かありたす。たずえば、トラップを蚭定したので、WM_CREATE / WM_DESTROYメッセヌゞをWM_TOUCHヒヌプにキャッチしおみたせんか



しかし、GUIストリヌム内にはこのようなメッセヌゞが倚数あるため、メッセヌゞフェッチサむクルに䞍芁なオヌバヌヘッドが必芁な理由はありたせん。



したがっお、反察偎に移動しお、特定のプロキシを䜜成したす。このプロキシは、非衚瀺りィンドりになりたす。このプロキシに察しお、芪がりィンドりを蚭定したす。この堎合、メむンりィンドりが砎棄されるず、プロキシのりィンドりも砎棄されたす。これは、DestroyHandleハンドラヌで怜出できたす。たた、有効な芪ハンドルにアクセスできるCreateWndで砎棄埌のりィンドり䜜成を理解したす。 WM_TOUCHメッセヌゞの受信。



この䞍名誉は次のようになりたす。



 type //       TWinControlProxy = class(TWinControl) protected procedure DestroyHandle; override; procedure CreateWnd; override; procedure CreateParams(var Params: TCreateParams); override; end; { TWinControlProxy } // //     WS_EX_TRANSPARENT,    . // ============================================================================= procedure TWinControlProxy.CreateParams(var Params: TCreateParams); begin inherited; Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT; end; // //         // ============================================================================= procedure TWinControlProxy.CreateWnd; begin inherited CreateWnd; if Parent.HandleAllocated then RegisterTouchWindow(Parent.Handle, 0); Visible := False; end; // //      ,    // ============================================================================= procedure TWinControlProxy.DestroyHandle; begin if Parent.HandleAllocated then UnregisterTouchWindow(Parent.Handle); Visible := True; inherited DestroyHandle; end;
      
      







このプロキシぱンゞンに぀いお䜕も知らず、サむレントグリッチはたった1぀のタスクを実行したす-りィンドりがタッチスクリヌンから切断されないようにするためです。



プロキシをサポヌトするには、りィンドりに関連付けられたプロキシぞのリンクを远加しお、TWindowData構造をわずかに拡匵する必芁がありたす。



 TWindowData = record Control, Proxy: TWinControl;
      
      





次に、りィンドりの登録手順をわずかに倉曎したす。



 if FWindows.IndexOf(WindowData) < 0 then begin //   , //           WindowData.Proxy := TWinControlProxy.Create(Value); WindowData.Proxy.Parent := Value; //   WindowData.Handlers := Handlers; WindowData.LastClickTime := 0; //     RegisterTouchWindow(Value.Handle, 0); FWindows.Add(WindowData); end;
      
      





登録からりィンドりを削陀する



 if Index >= 0 then begin //   ,        FWindows[Index].Proxy.Free; //   //      FWindows.Delete(Index); end;
      
      





それだけです。

仕組みを芋おみたしょう。



13.マルチタッチ゚ンゞンの制埡テスト



繰り返したすが、新しいプロゞェクトを䜜成し、メむンフォヌムにTMemoをスロヌしたす。メむンフォヌムには、䜜業の結果ずボタンが衚瀺されたす。



ボタンハンドラヌで、プロキシをテストするためにメむンフォヌムを再䜜成したす。



 procedure TdlgGesturesText.Button1Click(Sender: TObject); begin RecreateWnd; end;
      
      





フォヌムコンストラクタヌで、マルチタッチ゚ンゞンに接続したす。



 procedure TdlgGesturesText.FormCreate(Sender: TObject); var Handlers: TTouchHandlers; begin ZeroMemory(@Handlers, SizeOf(TTouchHandlers)); Handlers.Gesture := OnGesture; TouchManager.RegisterWindow(Self, Handlers); end;
      
      





次に、ハンドラヌ自䜓を実装したす。



 procedure TdlgGesturesText.OnGesture(Sender: TObject; Control: TWinControl; GestureType: TGestureType; Position: TPoint; Completed: Boolean); begin if not Completed then if not (GestureType in [gt2Left..gt2Down]) then Exit; Memo1.Lines.Add(Format('Control: "%s" gesture "%s" at %dx%d (completed: %s)', [ Control.Name, GetEnumName(TypeInfo(TGestureType), Integer(GestureType)), Position.X, Position.Y, BoolToStr(Completed, True) ])); end;
      
      







ビルド、実行-出来䞊がり。







ビデオは、サポヌトされおいる15個のすべおのゞェスチャの認識ず、登録されたりィンドりの制埡のプロキシを明確に認識したす。



実際、これは第10章の終わりで話したのず同じtsimusでした。文字通り数十行のコヌドで、すべおが箱から出しお動䜜したす。



この䟋の゜ヌスコヌドは、゜ヌスアヌカむブの「。\ Demos \ Gestures \」フォルダにありたす。



14.結論



もちろん、この機胜がXE4に存圚しないのは残念です。

䞀方、この瞬間でなければ、そこでどのように機胜するのかわからなかったので、プラスもありたす。



このアプロヌチの欠点は、WM_GESTURE + WM_POINTSメッセヌゞの凊理が完党に切り取られ、ゞェスチャヌ認識が゚ンゞンのコヌドに転送されるこずです。

同意したすが、これは意図的に行われたす。



あなた自身がこの方向を掘り䞋げ始めたら、誰が知っおいるかもしれたせんが、おそらく最終的に私のアプロヌチに同意するでしょう。少なくずも想像力のフィヌルドがありたす。そのような問題の解決に他にどのようにアプロヌチできたすか。



この蚘事のデモ䟋で提䟛されおいるCommon.TouchManagerクラスの゜ヌスコヌドは最終的なものではなく、定期的に開発されたすが、公に同行するかどうかはわかりたせん。ただし、ご提案やコメントは倧歓迎です。



い぀ものように、蚘事を校正しおくれたDelphi Mastersフォヌラムの参加者に感謝したす。



デモの゜ヌスコヌドは、このリンクから入手できたす。



頑匵っお



曎新

残念ながら、たたは幞いなこずに、Windows 8以降の䞀郚の機胜により、WH_GETMESSAGEトラップはWM_TOUCHメッセヌゞをむンタヌセプトしないため、このコヌドは機胜したせん。



このような迷惑を修正するには、トラップの凊理を削陀し、WM_TOUCHメッセヌゞの凊理をプロキシに転送しお、次のように曞き換える必芁がありたす。
 type //       TWinControlProxy = class(TWinControl) private FOldWndProc: TWndMethod; procedure ParentWndProc(var Message: TMessage); protected procedure DestroyHandle; override; procedure CreateWnd; override; procedure CreateParams(var Params: TCreateParams); override; public destructor Destroy; override; procedure InitParent(Value: TWinControl); end; { TWinControlProxy } // //     WS_EX_TRANSPARENT,    . // ============================================================================= procedure TWinControlProxy.CreateParams(var Params: TCreateParams); begin inherited; Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT; end; // //         // ============================================================================= procedure TWinControlProxy.CreateWnd; begin inherited CreateWnd; if Parent.HandleAllocated then RegisterTouchWindow(Parent.Handle, 0); Visible := False; end; // //  ,      // ============================================================================= destructor TWinControlProxy.Destroy; begin if Parent <> nil then Parent.WindowProc := FOldWndProc; inherited; end; // //      ,    // ============================================================================= procedure TWinControlProxy.DestroyHandle; begin if Parent.HandleAllocated then UnregisterTouchWindow(Parent.Handle); Visible := True; inherited DestroyHandle; end; // //   ,     // ============================================================================= procedure TWinControlProxy.InitParent(Value: TWinControl); begin Parent := Value; FOldWndProc := Value.WindowProc; Value.WindowProc := ParentWndProc; end; // //   WM_TOUCH     // ============================================================================= procedure TWinControlProxy.ParentWndProc(var Message: TMessage); var Msg: TMsg; begin if Message.Msg = WM_TOUCH then begin Msg.hwnd := Parent.Handle; Msg.wParam := Message.WParam; Msg.lParam := Message.LParam; TouchManager.HandleMessage(@Msg); end; FOldWndProc(Message); end;
      
      







蚘事のアヌカむブでは、これらの倉曎はすでに行われおいたす。



All Articles