inkpictureでgestureを認識したいです。

タグの編集
投稿者 snowmansnow  (社会人) 投稿日時 2020/4/1 22:58:50
よろしくお願いします。
win10_64bit、excel365のVBAです
inkDivider,
inkEdit
TabletPCに参照を入れています。
文字の認識などは、webなどを参考にできるようになりました。

'https://flylib.com/books/en/2.187.1/using_gestures_to_control_tablet_media_player.html
を見て、ジェスチャを認識したくなりました。

inkpicture1.setgesturestatus( IAG_AllGestures,true)でエラーが出ます。
これは右辺で、左辺が必要なのでしょうか?
gestureのイベントもわからないですし、
getgesturestatusの使い方もわかりません。
古い話題で申し訳ございませんが、昔、御回答をされてた事があったようで、
お願いしたいです。



投稿者 るきお  (社会人) 投稿日時 2020/4/2 08:47:49
エラーメッセージの記載をお願いします。

それに加えて、できましたら、そのエラーが発生するプログラムをまるごと投稿していただければ、記載されていない情報を推理・推測・再現する手間と能力がはぶけるため回答してくれる人が増えるかもしれません。

>inkpicture1.setgesturestatus( IAG_AllGestures,true)でエラーが出ます。
>これは右辺で、左辺が必要なのでしょうか?
構文上は右辺・左辺の問題でエラーになるものではありません。

私はInkやジェスチャーのことはほとんど知らないのですが、このような情報もあるようです。
https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/advanced/how-to-recognize-application-gestures

この情報はWPFで、サンプルがC#なので簡単には乗り換えられないかもしれませんが…。
投稿者 snowmansnow  (社会人) 投稿日時 2020/4/2 12:38:08
るきおさんありがとうございます。

Private Sub CommandButton2_Click()
            InkPicture1.CollectionMode = ICM_GestureOnly

            InkPicture1.setGestureStatus(IAG_AllGestures,True) ※1
            '上記が赤くなって
      'コンパイルエラー
            '修正候補 =
            'というエラーがIDE?VBE?から出ます。
            
            If InkPicture1.GetGestureStatus(IAG_AllGestures) Then
             MsgBox "maru"
            Else
            MsgBox InkPicture1.GetGestureStatus(IAG_AllGestures)
            End If
End Sub

     ※1を
      InkPicture1.SetGestureStatus(IAG_AllGestures, True) = 0にすると
     「コンパイルエラー、定数には値を代入できません。」
     になり、魔界さんが10年前に、筆圧の回答の中で、
     ’http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=9918
     なんちゃらに定数を指定~と書いてらっしゃったので、
     右辺(セット)、左辺(何かわかりません。型?)かなぁ?と思ったものです。

    るきおさん、いかがでしょうか?

     'https://flylib.com/books/en/2.187.1/using_gestures_to_control_tablet_media_player.htmlに
     タブレットSDK?のCD?に、参照したコードがあるような事も書いてありまして、
     重鎮の皆さんは、お持ちではないでしょうか?

    よろしくお願い致します。

     ’
 


投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/4/2 13:36:09
ジェスチャーを受け入れる場合は、CollectionMode プロパティを変更する必要があります。
(このプロパティの初期値は ICM_InkOnly なので、そのままだとジェスチャーを受け取れません)


> inkpicture1.setgesturestatus( IAG_AllGestures,true)でエラーが出ます。

それは VBA の文法構文になっていないので、コンパイルエラーになるでしょう。


> これは右辺で、左辺が必要なのでしょうか?

いいえ。COM 版 InkPicture の SetGestureStatus メソッドは
Function プロシージャーではなく、Sub プロシージャーなメソッドです。

たとえば IAG_Circle を指定する場合、VBA から呼び出す場合の構文は
Private Sub CommandButton1_Click()
    InkPicture1.SetGestureStatus IAG_Circle, True
End Sub

もしくは
Private Sub CommandButton1_Click()
    Call InkPicture1.SetGestureStatus(IAG_Circle, True)
End Sub

と書きます。


VBA においてメソッドを呼び出す場合、それが Sub である場合は
  foo.Method0
  foo.Method1 arg1
  foo.Method2 arg1, arg2
  foo.Method3 arg1, arg2, arg3
もしくは、Call ステートメントを併用して
  Call foo.Method0
  Call foo.Method1(arg1)
  Call foo.Method2(arg1, arg2)
  Call foo.Method3(arg1, arg2, arg3)
とします。

Function メソッドの場合は上記に加えて
  Let x = foo.Method0
  Let x = foo.Method1(arg1)
  Let x = foo.Method2(arg1, arg2)
  Let x = foo.Method3(arg1, arg2, arg3)
もしくは
  Set x = foo.Method0
  Set x = foo.Method1(arg1)
  Set x = foo.Method2(arg1, arg2)
  Set x = foo.Method3(arg1, arg2, arg3)
の構文を利用できます。
戻り値がオブジェクト型の場合は Set を使い、それ以外の場合は Let を使います。

ただし Let を使うことは稀です。Let は省略可能であるため、
非オブジェクト型の場合は一般的に
  x = foo.Method2(arg1, arg2)
と書きます。



> getgesturestatusの使い方もわかりません。

特定のジェスチャーが対象になっているかどうかを返すだけなので、
たとえば『If InkPicture1.GetGestureStatus(IAG_Circle) Then』で良いですよ。
ジェスチャーを対象にするのは SetGestureStatus です。

なお、すべてのジェスチャーを意味する IAG_AllGestures フラグは
SetGestureStatus はできますが、
GetGestureStatus はできません。個々のジェスチャーに対して判定してください。


> gestureのイベントもわからないですし、
ジェスチャーが認識されると、Gesture イベントが発生します。

Private Sub InkPicture1_Gesture(ByVal Cursor As MSINKAUTLib.IInkCursor, ByVal Strokes As MSINKAUTLib.IInkStrokes, ByVal Gestures As Variant, Cancel As Boolean)
    Debug.Print Gestures(0).ID, MSINKAUTLib.InkApplicationGesture.IAG_NoGesture
    If Gestures(0).ID <> MSINKAUTLib.InkApplicationGesture.IAG_NoGesture Then
        Dim newGuid As String
        newGuid = 識別用に生成したGuid値
        Strokes(0).ExtendedProperties.Add newGuid, Gestures(0).ID
        Cancel = True
    End If
End Sub



ここで使用する Guid は自己管理してください。
なお従来は、Guid を生成するために
    With CreateObject("Scriptlet.TypeLib")
        newID = Left(.Guid, 38)
    End With
というコードを使えたのですが、この方法は、
2018/07/31 更新の CVE-2017-8570 によって、VBA からは使えなくなりました。
https://docs.microsoft.com/ja-jp/archive/blogs/office_client_development_support_blog/201707secupdate-cannot-create-object

そのため現状の VBA では、OLE32.DLL の CoCreateGuid API を Declare して生成する必要があります。

Private Declare PtrSafe Function CoCreateGuid Lib "ole32.dll" (ByRef pGuid As Currency) As LongPtr
Private Declare PtrSafe Function StringFromGUID2 Lib "ole32.dll" (ByRef pGuid As Currency, ByVal lpStrGuid As LongPtr, ByVal cbMax As LongAs LongPtr
Public Function CreateGuid() As String
    Dim id(1) As Currency
    CreateGuid = "{00000000-0000-0000-0000-000000000000}"
    If CoCreateGuid(id(0)) = 0 Then
        Dim s As String
        s = String(39, 0)
        If StringFromGUID2(id(0), StrPtr(s), 39) <> 0 Then
            CreateGuid = Split(s, vbNullChar, 2)(0)
        End If
    End If
End Function
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/4/2 14:16:46
VBA において、引数ひとつのメソッドを呼び出す際に、丸括弧をつけてはいけません。
括弧を付ける場合は、Call ステートメントを使うか、戻り値を受け取る必要があります。

引数がひとつの場合、括弧をつけてもコンパイルは通りますが、意味が変わってしまうことがあります。

Sub Test()
    Dim s1 As String
    s1 = "あいうえお"
    Example s1      '括弧をつけずに呼び出す 
    MsgBox s1       'New String になる 
    
    Dim s2 As String
    s2 = "かきくけこ"
    Example (s2)    '括弧を付けて呼び出す 
    MsgBox s2       'かきくけこ のまま 
End Sub

Sub Example(x As String)
    x = "New String"
End Sub




特に、2 個以上の引数を必要とする場合は要注意。

'MsgBox (txt, vbInformation)         ' これは文法エラー 

MsgBox txt, vbInformation            ' 一般的な書き方 
Call MsgBox(txt, vbInformation)      ' 上記を Call 構文で書いた場合 
ret = MsgBox(txt, vbInformation)     ' 上記の戻り値を受け取る場合 

MsgBox (txt), (vbInformation)        ' 呼び出せるが、ByRef な引数の場合は意味が変わってしまう 
Call MsgBox((txt), (vbInformation))  ' 上記を Call 構文で書いた場合 
ret = MsgBox((txt), (vbInformation)) ' 上記の戻り値を受け取る場合 
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/4/2 14:33:34
魔界さんと呼ぶのは勘弁して貰いたいかな…。
たとえば「東村山の志村けんさん」を「東村山さん」と呼ぶのは違和感がありませんか? 🙃

そりゃまぁ、「アメリカのトランプさん」を「アメリカさん」と呼ぶようなケースが
全く無いとは言いませんけれど…。


> 魔界さんが10年前に、筆圧の回答の中で、
> ’http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=9918
> なんちゃらに定数を指定~と書いてらっしゃったので、

それは、.NET で使用される Managed な InkPicture コントロールですね。
VBA が使用される、ActiveX/COM な InkPicture コントロールとは別物です。

まぁ、内部実装を辿れば同じ物に行き着くことになるでしょうし、
良く似通ったコードになる可能性は高いのですけども、
必ずしも同じコードでそのまま置き換えられるとは限りません。


> If InkPicture1.GetGestureStatus(IAG_AllGestures) Then
先の回答にも記載した通り、.GetGestureStatus(IAG_AllGestures) という呼び出しは NG です。

https://docs.microsoft.com/ja-jp/windows/win32/api/msinkaut/nf-msinkaut-iinkpicture-getgesturestatus


>> Remarks:
>> This method throws an exception if the gesture parameter is set to IAG_AllGestures.
>> To set the interest of the InkPicture control in a particular gesture, call the SetGestureStatus Method.

以下意訳。

| 備考:
| gesture 引数に IAG_AllGestures が指定されると、GetGestureStatus メソッドは例外を発生させます。
| 特定のジェスチャーを InkPicture コントロールの対象とするには、SetGestureStatus メソッドを呼び出してください。
投稿者 snowmansnow  (社会人) 投稿日時 2020/4/2 15:33:33
仮面弁士さんこんにちは
昔から名前は存じ上げていましたが、まさかお話できるとは、嬉しいです。
るきおさんにも感謝しております。

また初歩的なものですが、

'https://wutils.com/com-dll/constants/constants-MSINKAUTLib.htm と
'https://docs.microsoft.com/en-us/windows/win32/tablet/application-gestures-and-semantic-behavior

 を参考に

Private Sub CommandButton2_Click()
           InkPicture1.CollectionMode = ICM_GestureOnly

           InkPicture1.SetGestureStatus IAG_Scratchout, True
           InkPicture1.SetGestureStatus IAG_Triangle, True
               ~
           InkPicture1.SetGestureStatus IAG_Exclamation, True
           InkPicture1.SetGestureStatus IAG_Tap, True
           InkPicture1.SetGestureStatus IAG_DoubleTap, True
    と全部列挙して①
          
            If geid = IAG_Circle Then
             MsgBox "maru"
            Else
             If geid = LeftDown Then
              MsgBox "ldown"
             Else
             End If
            End If
End Sub

    ②debug.printのところを
     パブリックの変数geidに代入に変更して、

     一回このボタンを押したら、その後は、
     msgboxでid表示にしても随時動くみたいなのですが、
 
     一番最初のinkがジェスチャーでなくインクになって画面に残っちゃいます。
     一回もボタンを押さないで、
        ICM_GestureOnly状態で
        SetGestureStatusも設定できないでしょうか?※1

    ③識別用に生成したGuid値は、教えて頂いた関数にしています。
    ④declareは、一番上に持って行きました。 

      ※1の解決がございましたら、教えていただきたいです。
         
       

       



投稿者 るきお  (社会人) 投稿日時 2020/4/2 16:15:21
VBAだったんですね。見落としていました。

名前は正確に書かないと失礼にあたりますよ。
親切に説明してくださっている回答者は魔界の仮面弁士さんです。
魔界さんや、仮面弁士さんではありません。
投稿者 snowmansnow  (社会人) 投稿日時 2020/4/2 18:02:27
魔界の仮面弁士さん、るきおさんごめんなさい。
質問以前ですね。ごめんなさい。

VBAでがんばっております。(javaもほんの少しだけ)
情報を参照すると15年とか10年前のものが多かったです。

フォームのイニシャライズにsetgesturestatusとかcollectionmodeとかをコピーしてみたのですが、
ボタンを押す前のストロークは、inkpicture1に描いたままの、ジェスチャ認識のようでした。
 (メッセージボックスを出しています)(認識はできるみたいです)

ボタンを押した後は、ジェスチャ認識になり、ストロークは消えますが、
その前のインクは、描いたままです。
 (メッセージボックスを出しています)

そこで、いただいたものを
Private Sub InkPicture1_Gesture(ByVal Cursor As MSINKAUTLib.IInkCursor, ByVal Strokes As MSINKAUTLib.IInkStrokes, ByVal Gestures As Variant, Cancel As Boolean)
    geid = Gestures(0).id
    Debug.Print Gestures(0).id ※1
    MsgBox Gestures(0).id  ※2
    
   InkPicture1.Ink.DeleteStrokes ※3
    Dim myInk As New MSINKAUTLib.InkDisp ※4
    InkPicture1.InkEnabled = False ※5
    Set InkPicture1.Ink = myInk ※6
    
   InkPicture1.InkEnabled = True
    
    
    If Gestures(0).id <> MSINKAUTLib.InkApplicationGesture.IAG_NoGesture Then
        Dim newGuid As String
        newGuid = CreateGuid
        Strokes(0).ExtendedProperties.Add newGuid, Gestures(0).id
        Cancel = True
    End If
End Sub

て、変えてみたのですが、
※3から※6は、読込時のリセットに使っています。これで消そうと思ったのですが、
 でもこれを入れると、ユーザーが操作や認識を行っている間は、操作を実行できません。
 になっちゃいます。

inkpicture1だけジェスチャで、inkpicture2、inkpicture3はinkにしています。
 inkpicture1は、ジェスチャだけで消えるものにしたいのですが、良い手はございますか?




投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/4/2 18:54:10
> と全部列挙して①
既定ではすべてのジェスチャーが無効になっているので、
使いたいジェスチャーのみを SetGestureStatus メソッドで True にします。

現状の設定が True/False いずれか判断するのは、GetGestureStatus ですが、
すべてを拾う前提なら、そもそも GetGestureStatus の出番は無いでしょう。

すべてのジェスチャーをまとめて設定したい場合は、IAG_AllGestures フラグで指定できます。

たとえば、DownUp/UpDown/Down/Up の 4 ジェスチャーだけを有効にしたい場合には、
.SetGestureStatus IAG_AllGestures, False
.SetGestureStatus IAG_DownUp, True
.SetGestureStatus IAG_UpDown, True
.SetGestureStatus IAG_Down, True
.SetGestureStatus IAG_Up, True
のようにできます。


> If geid = IAG_Circle Then
> ②debug.printのところを
> パブリックの変数geidに代入に変更して、
この部分、geid によって何を判定しようとしているのでしょうか?


> 一回このボタンを押したら、その後は、
> msgboxでid表示にしても随時動くみたいなのですが、
CommandButton2_Click に設定処理を書いているという事は、
ジェスチャーの有効化と無効化を切り替えられるようにしたいということでしょうか?


> ③識別用に生成したGuid値は、教えて頂いた関数にしています。
> ④declareは、一番上に持って行きました。 
この辺は今は使わないかも。ストロークに任意の管理データを割り当てる必要が無い場合には、
そもそも ExtendedProperties を使う必要もありません。

それと…済みません。先ほどの Declare の戻り値が間違っていました。

CoCreateGuid 関数の戻り値は HRESULT なので、As LongPtr ではなく As Long が正しいです。
StringFromGUID2 関数の戻り値は int なので、これも As LongPtr ではなく As Long が正しいです。



> 一番最初のinkがジェスチャーでなくインクになって画面に残っちゃいます。
Stroke イベントや Gesture イベントが発生するかどうかは、
CollectionMode プロパティに左右されます。

ICM_GestureOnly なら Gesture イベントのみ
ICM_InkOnly なら Stroke イベントのみ
ICM_InkAndGesture なら両方です。


ICM_GestureOnly によるジェスチャーは、マルチストローク(たとえば IAG_Exclamation など)をサポートします。
そのためジェスチャーパターンによって、操作し終わってから実際に解析されるまでに、
1 秒程度の待ち時間が必要になることがあります。
その待ち時間が経過するまでの間はストロークが残りますが、ジェスチャー判定後に消えると思います。

ICM_InkAndGesture によるジェスチャーでは、単一ストロール(たとえば IAG_DownUp など)しかサポートされませんので、
ストロークが終わると即座に Gesture イベントが発生します。ジェスチャーとして認識されたけれども
ジェスチャーではなくインクとして残したい場合は、引数 Cancel を True にします。
引数 Cancel を False にした場合は、そのままジェスチャーとして扱われ、End Sub 到達後に消えます。



なお、ジェスチャーが入力された時にどういう動作を行うかは
御自身で実装いただくことになります。

ジェスチャーによっては、「挿入」「削除」「コピー」「ペースト」などといった、
アプリケーション用に推奨されたジェスチャーが存在しますので、参考にしてみてください。
https://docs.microsoft.com/en-us/windows/win32/tablet/application-gestures-and-semantic-behavior
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/4/2 19:05:22
今回はジェスチャーなので関係ないと思いますが、描画したインクデータを
保持しておきたい場合には、Save メソッドを使うことができます。

Private inkData() As Byte
Private Sub CommandButton1_Click()
    If InkPicture1.InkEnabled Then
        inkData = InkPicture1.ink.Save(MSINKAUTLib.InkPersistenceFormat.IPF_InkSerializedFormat, MSINKAUTLib.InkPersistenceCompressionMode.IPCM_NoCompression)
    End If
End Sub



ストロークに対して付与しておいた ExtendedProperty を取り出したい場合には、
.ExtendedProperties.Add 時に渡した Guid 値を渡して呼び出します。

    Dim ink As MSINKAUTLib.InkDisp
    Set ink = New MSINKAUTLib.InkDisp
    ink.Load inkData
    Dim Stroke As MSINKAUTLib.IInkStrokeDisp
    For Each Stroke In ink.Strokes
        If Stroke.ExtendedProperties.DoesPropertyExist(inkExtendedGuid1) Then
             v = Stroke.ExtendedProperties(inkExtendedGuid1).Data
        ElseIf Stroke.ExtendedProperties.DoesPropertyExist(inkExtendedGuid2) Then
             v = Stroke.ExtendedProperties(inkExtendedGuid2).Data
        End If
    Next



※InkPicture についてはあまり詳しくないので、私自身も調べながら書いてます。
 間違った方向に誘導していたらごめんなさい。
投稿者 snowmansnow  (社会人) 投稿日時 2020/4/2 20:47:33
大変ありがとうございます

Sub UserForm_Initialize()

 InkPicture1.CollectionMode = ICM_GestureOnly
  InkPicture2.CollectionMode = ICM_InkOnly
  InkPicture3.CollectionMode = ICM_InkOnly
  
         
 InkPicture1.SetGestureStatus IAG_AllGestures, True
 

End Sub

Private Sub InkPicture1_Gesture(ByVal Cursor As MSINKAUTLib.IInkCursor, ByVal Strokes As MSINKAUTLib.IInkStrokes, ByVal Gestures As Variant, Cancel As Boolean)
    geid = Gestures(0).id ※
    Debug.Print Gestures(0).id
    MsgBox Gestures(0).id ※
    
    If Gestures(0).id <> MSINKAUTLib.InkApplicationGesture.IAG_NoGesture Then
        Dim newGuid As String
        newGuid = CreateGuid
        Strokes(0).ExtendedProperties.Add newGuid, Gestures(0).id
        Cancel = True
    End If

    InkPicture1.Ink.DeleteStrokes ※
   InkPicture1.InkEnabled = True ※
    
End Sub
にしてみましたら 思った動きになりました。実装は、後でやってみます!

いただいたsaveは、ファイル名が無かったので、どうSAVEされるのかよくわかりませんでした。
どこかにSAVE順に積み重ねて保存される(PUSH?みたいな保存)のでしょうか?
このタイプのLOADも、まったく思いつきませんでした。
BYTEの、PUTでSAVE、GETでLOADは、事前に出来るようになりました。

大変お世話になり、有難かったです。
何度も親切に大変ありがとうございました。

別件で質問があり、またよろしくお願いしたいです。