Windows7のマルチタッチジェスチャを使う

とりあえずそれっぽい動きをしたので簡単なメモを残す。
今回やったのはマルチタッチジェスチャ。タッチ認識ではないほう。

基本的なこと

  • ウィンドウメッセージ WM_GESTURE でジェスチャコマンドが送られてくる
  • lParam を GetGestureInfo に渡して GESTUREINFO 構造体でデータを得る
  • GESTUREINFO.dwID で何のジェスチャが実行されたか判別する。GCI_xxxx が対応する定数。
  • GESTUREINFO.ptsLocation とかで位置を取れるけど、その位置が何を示すかはよくわからなかった。

ドキュメントとか

とりあえず、自分が動かしたコード

Form1.cs (真ん中にpictureBox1をおいたフォーム)
using System;
using System.Drawing;
using EbiSoft.Library;

namespace WindowsFormsApplication1
{
    public partial class Form1 : TouchableForm
    {
        private Point beginLocation = new Point(0, 0);
        private Size baseSize;
        private Point startCenter;
        private double baseDelta;

        public Form1()
        {
            InitializeComponent();
            this.GestureBegin += new EventHandler<GestureEventArgs>(Form1_GestureBegin);
            this.GestureZoom += new EventHandler<GestureEventArgs>(Form1_GestureZoom);

            // 基準となるパラメータを記録する
            baseSize = pictureBox1.Size;
            startCenter = new Point(pictureBox1.Location.X + (baseSize.Width / 2), pictureBox1.Location.Y + (baseSize.Height / 2));
            baseDelta = Math.Sqrt(Math.Pow((double)baseSize.Width, 2) + Math.Pow((double)baseSize.Height, 2));
        }

        void Form1_GestureBegin(object sender, GestureEventArgs e)
        {
            // ジェスチャ開始位置を記録する
            beginLocation = e.Location;
        }

        void Form1_GestureZoom(object sender, GestureEventArgs e)
        {
            double sizeDelta = (baseDelta + Math.Sqrt(Math.Pow((double)(e.Location.X - beginLocation.X), 2) + Math.Pow((double)(e.Location.Y - beginLocation.Y), 2))) / baseDelta;

            // 新しいサイズにする
            pictureBox1.Size = new Size((int)(baseSize.Width * sizeDelta), (int)(baseSize.Height * sizeDelta));

            // 新しい中心点を得る
            pictureBox1.Location = new Point(startCenter.X - (pictureBox1.Size.Width / 2), startCenter.Y - (pictureBox1.Size.Height / 2));
        }
    }
}

TouchableForm.cs

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;

namespace EbiSoft.Library
{
    public class TouchableForm : Form
    {
        #region P/Invoke 宣言

        // タッチ有効・無効の切り替え
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool RegisterTouchWindow(System.IntPtr hWnd, ulong ulFlags);
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnregisterTouchWindow(System.IntPtr hWnd, ulong ulFlags);

        // ジェスチャ有効・無効の切り替え
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool RegisterGestureHandlerWindow(System.IntPtr hWnd, ulong ulFlags);
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnregisterGestureHandlerWindow(System.IntPtr hWnd, ulong ulFlags);

        // ジェスチャ詳細取得・開放
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetGestureInfo(IntPtr hGestureInfo, ref GESTUREINFO pGestureInfo);
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseGestureInfoHandle(IntPtr hGestureInfo);        

        // ウィンドウメッセージ
        private const int WM_GESTURE = 0x0119;

        // ジェスチャフラグ
        private const int GF_BEGIN = 0x00000001;
        private const int GF_INERTIA = 0x00000002;
        private const int GF_END = 0x00000004;


        // ジェスチャコマンド
        private const int GID_BEGIN = 1;
        private const int GID_END = 2;
        private const int GID_ZOOM = 3;
        private const int GID_PAN = 4;
        private const int GID_ROTATE = 5;
        private const int GID_TWOFINGERTAP = 6;
        private const int GID_ROLLOVER = 7;

        // ポイント
        [StructLayout(LayoutKind.Sequential)]
        private struct POINTS
        {
            public short x;
            public short y;
        }

        // ジェスチャ情報
        [StructLayout(LayoutKind.Sequential)]
        private struct GESTUREINFO
        {
            public int cbSize;
            public int dwFlags;
            public int dwID;
            public IntPtr hwndTarget;
            [MarshalAs(UnmanagedType.Struct)]
            public POINTS ptsLocation;
            public int dwInstanceID;
            public int dwSequenceID;
            public UInt64 ullArguments;
            public uint cbExtraArgs;
        }

        #endregion

        #region イベントハンドラ
        public event EventHandler<GestureEventArgs> GestureBegin;
        public event EventHandler<GestureEventArgs> GestureEnd;
        public event EventHandler<GestureEventArgs> GestureZoom;
        public event EventHandler<GestureEventArgs> GesturePan;
        public event EventHandler<GestureEventArgs> GestureRotate;
        public event EventHandler<GestureEventArgs> GestureTwoFingerTap;
        public event EventHandler<GestureEventArgs> GestureRollover;
        #endregion

        private bool canTouch;
        private bool canGesture;

        #region 初期化・開放

        public TouchableForm() : base()
        {
            // タッチが有効かどうか確認する
            try
            {
                if (RegisterTouchWindow(this.Handle, 0))
                {
                    canTouch = true;
                    UnregisterTouchWindow(this.Handle, 0);
                }
                else
                {
                    canTouch = false;
                }
            }
            catch (Exception)
            {
                canTouch = false;
            }

            // ジェスチャが有効かどうか確認する
            try
            {
                if (RegisterGestureHandlerWindow(this.Handle, 0))
                {
                    canGesture = true;
                }
                else
                {
                    canGesture = false;
                }
            }
            catch (Exception)
            {
                canGesture = false;
            }

        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                UnregisterGestureHandlerWindow(this.Handle, 0);
                UnregisterTouchWindow(this.Handle, 0);
            }
            base.Dispose(disposing);
        }

        #endregion

        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_GESTURE:
                    DecodeGesture(ref m);
                    break;
                default:
                    break;
            }

            base.WndProc(ref m);
        }

        private void DecodeGesture(ref Message m)
        {
            GESTUREINFO gi = new GESTUREINFO();
            gi.cbSize = Marshal.SizeOf(typeof(GESTUREINFO));

            if (GetGestureInfo(m.LParam, ref gi))
            {
                try
                {
                    Point gestureLocation = new Point(gi.ptsLocation.x, gi.ptsLocation.y);
                    switch (gi.dwID)
                    {
                        case GID_BEGIN:
                            System.Diagnostics.Debug.WriteLine("GID_BEGIN");
                            if (GestureBegin != null)
                                GestureBegin(this, new GestureEventArgs(gestureLocation));
                            break;
                        case GID_END:
                            System.Diagnostics.Debug.WriteLine("GID_END");
                            if (GestureEnd != null)
                                GestureEnd(this, new GestureEventArgs(gestureLocation));
                            break;
                        case GID_ZOOM:
                            System.Diagnostics.Debug.WriteLine("GID_ZOOM");
                            if (GestureZoom != null)
                                GestureZoom(this, new GestureEventArgs(gestureLocation));
                            break;
                        case GID_PAN:
                            System.Diagnostics.Debug.WriteLine("GID_PAN");
                            if (GesturePan != null)
                                GesturePan(this, new GestureEventArgs(gestureLocation));
                            break;
                        case GID_ROTATE:
                            System.Diagnostics.Debug.WriteLine("GID_ROTATE");
                            if (GestureRotate != null)
                                GestureRotate(this, new GestureEventArgs(gestureLocation));
                            break;
                        case GID_TWOFINGERTAP:
                            System.Diagnostics.Debug.WriteLine("GID_TWOFINGERTAP");
                            if (GestureTwoFingerTap != null)
                                GestureTwoFingerTap(this, new GestureEventArgs(gestureLocation));
                            break;
                        case GID_ROLLOVER:
                            System.Diagnostics.Debug.WriteLine("GID_ROLLOVER");
                            if (GestureRollover != null)
                                GestureRollover(this, new GestureEventArgs(gestureLocation));
                            break;
                        default:
                            // You have encountered an unknown gesture
                            break;
                    }
                }
                finally
                {
                    CloseGestureInfoHandle(m.LParam);
                }
            }
        }

        #region プロパティ

        // ジェスチャ機能が使用可能かどうかを取得します。
        public bool CanGesture
        {
            get { return canGesture; }
        }

        /// <summary>
        /// タッチ機能が有効かどうかを取得します。
        /// </summary>
        public bool CanTouch
        {
            get { return canTouch; }
        }

        #endregion
    }


    /// <summary>
    /// ジェスチャのイベントデータ
    /// </summary>
    public class GestureEventArgs : System.EventArgs
    {
        //------------------------
        // Private members
        //------------------------
        private Point location;

        //------------------------
        // Public members
        //------------------------
        public Point Location
        {
            get { return location; }
        }

        //-----------------------
        // Public methods
        //------------------------
        public GestureEventArgs(Point location)
        {
            this.location = location;
        }
    }

    /// <summary>
    /// タッチのイベントデータ
    /// </summary>
    public class TouchEventArgs : System.EventArgs
    {
        //------------------------
        // Private members
        //------------------------
        private int id;
        private int mask;
        private int time;
        private Point location;
        private Point contactLocation;

        //------------------------
        // Public members
        //------------------------
        public Point Location
        {
            get { return location; }
        }

        public Point ContactLocation
        {
            get { return contactLocation; }
        }

        public int Id
        {
            get { return id; }
        }

        public int Mask
        {
            get { return mask; }
        }

        public int Time
        {
            get { return time; }
        }

        //-----------------------
        // Public methods
        //------------------------
        public TouchEventArgs(Point location, int id, int mask, int time, Point contactLocation)
        {
            this.location = location;
            this.id = id;
            this.mask = mask;
            this.time = time;
            this.contactLocation = contactLocation;
        }
    }
}