windowsのデスクトップアプリでは、Surfaceなどタブレット端末で使用するジェスチャーイベントに対応していません。
そこで、「Dotspatial」にジェスチャーイベントを追加し、タブレットでも操作できるようにしました。
以下はその時のメモです。
ジェスチャーイベントの追加は、以下マイクロソフトから提供されている
デスクトップ アプリケーション のSamples/Win7Samples/Touch/MTGesturesを参考にしました。
↓
https://github.com/Microsoft/Windows-classic-samples
今回追加するジェスチャーイベントは以下の2種類です。
ジェスチャー名 | アクション説明 | アクションイメージ | 対象 |
ZOOM | 2本の指を使って、間隔を広げたり、狭めたりします。 | ○ | |
PAN | 1本または2本の指を使って、上/下または左/右にドラッグしてポイントを動かします。 | ○ | |
ROTATE | 2本の指を使って、ポイントを回転させます。 | × | |
TWOFINGERTAP | 2本の指を使って、同時にタップします。 | × | |
PRESSANDTAP | 1本の指で押しながら、他の指でタップします。 | × |
1.GestureListenerの作成
GestureListenerクラスを作成して、.NETのcontrolクラス(今回は「Dotspatial」のMapクラス)のジェスチャーイベントの登録及び取得を行います。
ジェスチャーイベントの登録及び情報の取得は、SetGestureConfig およ び GetGestureConfig というWindows API を介して行います。
.NETのcontrolクラスが画面上のジェスチャを検出するたびに、GetGestureinfo API を呼び出すことで、どのようなジェスチャを実行しようとしているのかが特定できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
namespace WinGestures { public class GestureListener { public event EventHandler Pan; public event EventHandler Zoom; private long _lastZoom; private Point _lastPanPoint; private const Int64 ULL_ARGUMENTS_BIT_MASK = 0x00000000FFFFFFFF; public const int WM_GESTURENOTIFY = 0x011A; public const int WM_GESTURE = 0x0119; private const int GC_ALLGESTURES = 0x00000001; // Gesture IDs 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_PRESSANDTAP = 7; // Gesture flags - GESTUREINFO.dwFlags private const int GF_BEGIN = 0x00000001; private const int GF_INERTIA = 0x00000002; private const int GF_END = 0x00000004; [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetGestureConfig(IntPtr hWnd, int dwReserved, int cIDs, ref GestureConfig pGestureConfig, int cbSize); [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetGestureInfo(IntPtr hGestureInfo, ref GestureInfo pGestureInfo); private int _gestureConfigSize; private int _gestureInfoSize; private Control control; public GestureListener(Control parent) { this.control = parent; this.SetupStructSizes(); } [SecurityPermission(SecurityAction.Demand)] private void SetupStructSizes() { _gestureConfigSize = Marshal.SizeOf(new GestureConfig()); _gestureInfoSize = Marshal.SizeOf(new GestureInfo()); } public bool SetGesture(IntPtr hWnd) { var handled = false; var gc = new GestureConfig(); gc.dwID = 0; gc.dwWant = GC_ALLGESTURES; gc.dwBlock = 0; handled = SetGestureConfig( hWnd, 0, 1, ref gc, _gestureConfigSize ); return handled; } // Handler of gestures //in: // m - Message object public bool DecodeGesture(ref Message m) { bool handled = false; var info = new GestureInfo(); info.size = _gestureInfoSize; bool bResult = GetGestureInfo(m.LParam, ref info); if (!bResult) { throw new Exception("Error in execution of GetGestureInfo"); } switch (info.id) { case GID_PAN: handled = this.OnPan(info); break; case GID_PRESSANDTAP: // handled = OnPressAndTap(info); break; case GID_ROTATE: // handled = OnRotate(info); break; case GID_TWOFINGERTAP: // handled = OnTwoFingerTap(info); break; case GID_ZOOM: handled = this.OnZoom(info); break; } return handled; } private bool OnPan(GestureInfo info) { if (this.Pan != null) { if (info.Begin) { this._lastPanPoint = new Point(info.location.x, info.location.y); } var args = new PanEventArgs(info, this._lastPanPoint); this._lastPanPoint = new Point(info.location.x, info.location.y); this.Pan(this, args); return args.Handled; } return false; } private bool OnZoom(GestureInfo info) { if (this.Zoom != null) { if (info.Begin) { _lastZoom = info.arguments; } var args = new ZoomEventArgs(info, _lastZoom); _lastZoom = args.Distance; this.Zoom(this, args); return args.Handled; } return false; } } } |
2.GestureInfoの作成
ジェスチャーイベントの登録及び取得に使用する構造体です。 SetGestureConfig およ び GetGestureConfig という Windows API を使用時に利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
namespace WinGestures { [StructLayout(LayoutKind.Sequential)] public struct GestureInfo { public int size; public int flags; public int id; public IntPtr hwnd; public Points location; public int instanceId; public int sequenceId; public Int64 arguments; public int extraArguments; public bool Begin { get { return ((GestureFlags)flags & GestureFlags.Begin) == GestureFlags.Begin; } } public bool End { get { return ((GestureFlags)flags & GestureFlags.End) == GestureFlags.End; } } public bool Inertia { get { return ((GestureFlags)flags & GestureFlags.Inertia) == GestureFlags.Inertia; } } } [Flags] public enum GestureFlags { Begin = 0x1, Inertia = 0x2, End = 0x4 } [StructLayout(LayoutKind.Sequential)] public struct Points { public short x; public short y; } [StructLayout(LayoutKind.Sequential)] public struct GestureConfig { public int dwID; public int dwWant; public int dwBlock; } } |
3.GestureEventArgsの作成
各種ジェスチャーイベント発火時に、引数として使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
namespace WinGestures { public abstract class GestureEventArgs : EventArgs { internal GestureEventArgs(GestureInfo info) { Location = new Point(info.location.x, info.location.y); Info = info; Handled = true; } protected static int LoDWord(IntPtr lParam) { return LoDWord(lParam.ToInt64()); } protected static int HiDWord(IntPtr lParam) { return HiDWord(lParam.ToInt64()); } protected static int LoDWord(long l) { return (int)(l & 0xFFFFFFFF); } protected static int HiDWord(long l) { return (int)((l >> 32) & 0xFFFFFFFF); } protected static short LoWord(int i) { return (short)(i & 0xFFFF); } protected static short HiWord(int i) { return (short)((i >> 16) & 0xFFFF); } protected GestureInfo Info { get; set; } public Point Location { get; private set; } public bool Begin { get { return Info.Begin; } } public bool End { get { return Info.End; } } public bool Handled { get; set; } } public class PanEventArgs : GestureEventArgs { internal PanEventArgs(GestureInfo info, Point lastPanPoint) : base(info) { int hiword = HiDWord(info.arguments); InertiaVector = new Point(LoWord(hiword), HiWord(hiword)); PanOffset = new Point(Location.X - lastPanPoint.X, Location.Y - lastPanPoint.Y); } public bool Inertia { get { return Info.Inertia; } } public Point InertiaVector { get; private set; } public Point PanOffset { get; private set; } } public class ZoomEventArgs : GestureEventArgs { internal ZoomEventArgs(GestureInfo info, long lastZoomDistance) : base(info) { Distance = info.arguments; PercentChange = (double)Distance / lastZoomDistance; } public long Distance { get; private set; } public double PercentChange { get; private set; } } } |
4.Mapの改訂
上記クラスをインポートしたうえで、ジェスチャーイベントが使えるよう、Mapクラスを改訂します。
※Mapクラスは改定箇所のみ記載しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
namespace DotSpatial.Controls { public partial class Map : UserControl, IMap, IMessageFilter { private GestureListener _gesture; private void Configure() { _gesture = new GestureListener(this); _gesture.Pan += this.OnPan; _gesture.Zoom += this.OnZoom; } [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] protected override void WndProc(ref Message m) { var handled = false; try { if (this._gesture != null) { switch (m.Msg) { case GestureListener.WM_GESTURENOTIFY: handled = this._gesture.SetGesture(Handle); break; case GestureListener.WM_GESTURE: handled = this._gesture.DecodeGesture(ref m); break; default: handled = false; break; } } base.WndProc(ref m); if (handled) { m.Result = new System.IntPtr(1); } } catch (Exception excep) { } } void OnPan(object sender, PanEventArgs e) { var rect = this.MapFrame.View; if (!e.Begin) { this.MapFrame.View = new Rectangle(rect.X - e.PanOffset.X, rect.Y - e.PanOffset.Y, rect.Width, rect.Height); this.MapFrame.ResetExtents(); } } void OnZoom(object sender, ZoomEventArgs e) { if (!e.Begin) { var rect = this.MapFrame.View; if (e.PercentChange != 1) { if (e.PercentChange < 1) { rect.Inflate(rect.Width / 20, rect.Height / 20); } else if (e.PercentChange > 1) { rect.Inflate(-rect.Width / 20, -rect.Height / 20); } this.MapFrame.View = rect; this.MapFrame.ResetExtents(); } } } } } |
以下、surfaceでの画面イメージです。
初期表示時
2本指を使ったズームイン
1本指を使ったパンによる移動