using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Drawing; using System.Runtime.InteropServices; using System.Data; namespace POSV.Keyboard.Touch { internal class TouchHandler { #region Events public event EventHandler PanBegin; /// /// Raises PanBegin event. /// /// Provides event arguments. protected virtual void OnPanBegin(GestureEventArgs e) { EventHandler handler = PanBegin; if (handler != null) handler(this, e); } public event EventHandler PanEnd; /// /// Raises PanBegin event. /// /// Provides event arguments. protected virtual void OnPanEnd(GestureEventArgs e) { EventHandler handler = PanEnd; if (handler != null) handler(this, e); } public event EventHandler Pan; /// /// Raises PanBegin event. /// /// Provides event arguments. protected virtual void OnPan(GestureEventArgs e) { EventHandler handler = Pan; if (handler != null) handler(this, e); } public event EventHandler Begin; /// /// Raises Begin event. /// /// Provides event arguments. protected virtual void OnBegin(GestureEventArgs e) { EventHandler handler = Begin; if (handler != null) handler(this, e); } public event EventHandler End; /// /// Raises End event. /// /// Provides event arguments. protected virtual void OnEnd(GestureEventArgs e) { EventHandler handler = End; if (handler != null) handler(this, e); } public event EventHandler PressAndTap; /// /// Raises PressAndTap event. /// /// Provides event arguments. protected virtual void OnPressAndTap(GestureEventArgs e) { EventHandler handler = PressAndTap; if (handler != null) handler(this, e); } public event EventHandler RotateBegin; /// /// Raises RotateBegin event. /// /// Provides event arguments. protected virtual void OnRotateBegin(GestureEventArgs e) { EventHandler handler = RotateBegin; if (handler != null) handler(this, e); } public event EventHandler Rotate; /// /// Raises Rotate event. /// /// Provides event arguments. protected virtual void OnRotate(GestureEventArgs e) { EventHandler handler = Rotate; if (handler != null) handler(this, e); } public event EventHandler RotateEnd; /// /// Raises RotateEnd event. /// /// Provides event arguments. protected virtual void OnRotateEnd(GestureEventArgs e) { EventHandler handler = RotateEnd; if (handler != null) handler(this, e); } public event EventHandler TwoFingerTap; /// /// Raises TwoFingerTap event. /// /// Provides event arguments. protected virtual void OnTwoFingerTap(GestureEventArgs e) { EventHandler handler = TwoFingerTap; if (handler != null) handler(this, e); } public event EventHandler ZoomBegin; /// /// Raises ZoomBegin event. /// /// Provides event arguments. protected virtual void OnZoomBegin(GestureEventArgs e) { EventHandler handler = ZoomBegin; if (handler != null) handler(this, e); } public event EventHandler Zoom; /// /// Raises Zoom event. /// /// Provides event arguments. protected virtual void OnZoom(GestureEventArgs e) { EventHandler handler = Zoom; if (handler != null) handler(this, e); } public event EventHandler ZoomEnd; /// /// Raises ZoomEnd event. /// /// Provides event arguments. protected virtual void OnZoomEnd(GestureEventArgs e) { EventHandler handler = ZoomEnd; if (handler != null) handler(this, e); } // Touch events public event EventHandler TouchDown; /// /// Raises TouchDown event. /// /// Provides event arguments. protected virtual void OnTouchDown(TouchEventArgs e) { EventHandler handler = TouchDown; if (handler != null) handler(this, e); } public event EventHandler TouchUp; /// /// Raises TouchDown event. /// /// Provides event arguments. protected virtual void OnTouchUp(TouchEventArgs e) { EventHandler handler = TouchUp; if (handler != null) handler(this, e); } public event EventHandler TouchMove; /// /// Raises TouchDown event. /// /// Provides event arguments. protected virtual void OnTouchMove(TouchEventArgs e) { EventHandler handler = TouchMove; if (handler != null) handler(this, e); } #endregion #region Constructor private Control _ParentControl = null; private eTouchHandlerType _HandlerType = eTouchHandlerType.Gesture; /// /// Initializes a new instance of the TouchHandler class. /// /// public TouchHandler(Control parentControl) : this(parentControl, eTouchHandlerType.Gesture) { } /// /// Initializes a new instance of the TouchHandler class. /// /// public TouchHandler(Control parentControl, eTouchHandlerType handlerType) { _ParentControl = parentControl; _HandlerType = handlerType; if (IsTouchEnabled) { if (_ParentControl.IsHandleCreated) Initialize(); else _ParentControl.HandleCreated += new EventHandler(ParentControlHandleCreated); } } #endregion #region Implementation void ParentControlHandleCreated(object sender, EventArgs e) { Initialize(); } private IntPtr _originalWindowProcId; private WinApi.WindowProcDelegate _windowProcDelegate; /// /// Initializes handler /// private void Initialize() { if (!RegisterTouchWindow()) { throw new NotSupportedException("Cannot register window"); } _windowProcDelegate = WindowProc; _originalWindowProcId = IntPtr.Size == 4 ? WinApi.SubclassWindow(_ParentControl.Handle, WinApi.GWLP_WNDPROC, _windowProcDelegate) : WinApi.SubclassWindow64(_ParentControl.Handle, WinApi.GWLP_WNDPROC, _windowProcDelegate); //take the desktop DPI using (Graphics graphics = Graphics.FromHwnd(_ParentControl.Handle)) { DpiX = graphics.DpiX; DpiY = graphics.DpiY; } } public Control ParentControl { get { return _ParentControl; } } /// /// The Windows message handler. /// private uint WindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam) { if(msg == WinApi.WM_TOUCH && (_HandlerType & eTouchHandlerType.Touch)== eTouchHandlerType.Touch) { foreach (TouchEventArgs arg in DecodeMessage(hWnd, msg, wParam, lParam, DpiX, DpiY)) { if (arg.IsTouchDown) OnTouchDown(arg); if (arg.IsTouchMove) OnTouchMove(arg); if (arg.IsTouchUp) OnTouchUp(arg); } return 1; } // Handle only gesture message if (msg != WinApi.WM_GESTURE) { return WinApi.CallWindowProc(_originalWindowProcId, hWnd, msg, wParam, lParam); } WinApi.GESTUREINFO gestureInfo = new WinApi.GESTUREINFO(); gestureInfo.cbSize = (uint)Marshal.SizeOf(typeof(WinApi.GESTUREINFO)); bool result = WinApi.GetGestureInfo(lParam, ref gestureInfo); if (!result) throw new Exception("Cannot retrieve gesture information"); //Decode the gesture info and get the message event argument GestureEventArgs eventArgs = new GestureEventArgs(this, ref gestureInfo); try { //Fire the event using the event map uint gestureId = GetGestureEventId(gestureInfo.dwID, gestureInfo.dwFlags); if (gestureId == GestureEventId.Begin) OnBegin(eventArgs); else if (gestureId == GestureEventId.End) OnEnd(eventArgs); else if (gestureId == GestureEventId.Pan) OnPan(eventArgs); else if (gestureId == GestureEventId.PanBegin) OnPanBegin(eventArgs); else if (gestureId == GestureEventId.PanEnd) OnPanEnd(eventArgs); else if (gestureId == GestureEventId.PressAndTap) OnPressAndTap(eventArgs); else if (gestureId == GestureEventId.Rotate) OnRotate(eventArgs); else if (gestureId == GestureEventId.RotateBegin) OnRotateBegin(eventArgs); else if (gestureId == GestureEventId.RotateEnd) OnRotateEnd(eventArgs); else if (gestureId == GestureEventId.TwoFingerTap) OnTwoFingerTap(eventArgs); else if (gestureId == GestureEventId.Zoom) OnZoom(eventArgs); else if (gestureId == GestureEventId.ZoomBegin) OnZoomBegin(eventArgs); else if (gestureId == GestureEventId.ZoomEnd) OnZoomEnd(eventArgs); } catch (ArgumentOutOfRangeException) //In case future releases will introduce new event values { } //Keep the last message for relative calculations LastEventArgs = eventArgs; //Keep the first message for relative calculations if (eventArgs.IsBegin) LastBeginEventArgs = eventArgs; WinApi.CloseGestureInfoHandle(lParam); return 1; } /// /// Decode the message and create a collection of event arguments /// private IEnumerable DecodeMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, float dpiX, float dpiY) { // More than one touch input can be associated with a touch message int inputCount = WinApi.LoWord(wParam.ToInt32()); // Number of touch inputs, actual per-contact messages WinApi.TOUCHINPUT[] inputs; // Array of TOUCHINPUT structures inputs = new WinApi.TOUCHINPUT[inputCount]; // Allocate the storage for the parameters of the per-contact messages try { // Unpack message parameters into the array of TOUCHINPUT structures, each representing a message for one single contact. if (!WinApi.GetTouchInputInfo(lParam, inputCount, inputs, Marshal.SizeOf(inputs[0]))) { // Touch info failed. throw new Exception("Error calling GetTouchInputInfo API"); } // For each contact, dispatch the message to the appropriate message handler. // For WM_TOUCHDOWN you can get down & move notifications and for WM_TOUCHUP you can get up & move notifications // WM_TOUCHMOVE will only contain move notifications and up & down notifications will never come in the same message for (int i = 0; i < inputCount; i++) { TouchEventArgs touchEventArgs = new TouchEventArgs(this, dpiX, dpiY, ref inputs[i]); yield return touchEventArgs; } } finally { WinApi.CloseTouchInputHandle(lParam); } } private GestureEventArgs _LastBeginEventArgs; /// /// The event arguments that started the current gesture /// internal GestureEventArgs LastBeginEventArgs { get { return _LastBeginEventArgs; } set { _LastBeginEventArgs = value; } } /// /// The last event in the current gesture event sequence /// private GestureEventArgs _LastEventArgs; internal GestureEventArgs LastEventArgs { get { return _LastEventArgs; } set { _LastEventArgs = value; } } private static class GestureEventId { public static readonly uint Begin = GetGestureEventId(WinApi.GID_BEGIN, 0); public static readonly uint End = GetGestureEventId(WinApi.GID_END, 0); public static readonly uint PanBegin = GetGestureEventId(WinApi.GID_PAN, WinApi.GF_BEGIN); public static readonly uint Pan = GetGestureEventId(WinApi.GID_PAN, 0); public static readonly uint PanEnd = GetGestureEventId(WinApi.GID_PAN, WinApi.GF_END); public static readonly uint PressAndTap = GetGestureEventId(WinApi.GID_PRESSANDTAP, 0); public static readonly uint RotateBegin = GetGestureEventId(WinApi.GID_ROTATE, WinApi.GF_BEGIN); public static readonly uint Rotate = GetGestureEventId(WinApi.GID_ROTATE, 0); public static readonly uint RotateEnd = GetGestureEventId(WinApi.GID_ROTATE, WinApi.GF_END); public static readonly uint TwoFingerTap = GetGestureEventId(WinApi.GID_TWOFINGERTAP, 0); public static readonly uint ZoomBegin = GetGestureEventId(WinApi.GID_ZOOM, WinApi.GF_BEGIN); public static readonly uint Zoom = GetGestureEventId(WinApi.GID_ZOOM, 0); public static readonly uint ZoomEnd = GetGestureEventId(WinApi.GID_ZOOM, WinApi.GF_END); } private static uint GetGestureEventId(uint dwID, uint dwFlags) { return (dwID << 3) + (dwID == WinApi.GID_TWOFINGERTAP || dwID == WinApi.GID_PRESSANDTAP || dwID == WinApi.GID_BEGIN || dwID == WinApi.GID_END ? 0 : dwFlags & 5); } /// /// Register for touch event /// /// true if succeeded private bool RegisterTouchWindow() { bool result = false; if ((_HandlerType & eTouchHandlerType.Gesture) == eTouchHandlerType.Gesture) { WinApi.GESTURECONFIG[] gestureConfig = new WinApi.GESTURECONFIG[] { new WinApi.GESTURECONFIG(0, WinApi.GC_ALLGESTURES, 0) }; result = WinApi.SetGestureConfig(_ParentControl.Handle, 0, 1, gestureConfig, (uint)Marshal.SizeOf(typeof(WinApi.GESTURECONFIG))); } if ((_HandlerType & eTouchHandlerType.Touch) == eTouchHandlerType.Touch) { result |= WinApi.RegisterTouchWindow(_ParentControl.Handle, _DisablePalmRejection ? WinApi.TouchWindowFlag.WantPalm : 0); } return result; } private bool _DisablePalmRejection; /// /// Gets or sets whether palm rejection is enabled. /// public bool DisablePalmRejection { get { return _DisablePalmRejection; } set { if (_DisablePalmRejection == value) return; _DisablePalmRejection = value; if (_ParentControl.IsHandleCreated) { WinApi.UnregisterTouchWindow(_ParentControl.Handle); RegisterTouchWindow(); } } } private float _DpiX; public float DpiX { get { return _DpiX; } set { _DpiX = value; } } private float _DpiY; public float DpiY { get { return _DpiY; } set { _DpiY = value; } } /// /// Check if Multi-touch support device is ready /// public static bool IsTouchEnabled { get { return (WinApi.GetDigitizerStatus() & (WinApi.DigitizerStatus.StackReady | WinApi.DigitizerStatus.MultiInput)) != 0; } } #endregion } [Flags] internal enum eTouchHandlerType : short { Gesture = 1, Touch = 2 } internal class GestureEventArgs : EventArgs { private readonly uint _Flags; /// /// Create new gesture event instance and decode the gesture info structure /// /// The gesture handler /// The gesture information internal GestureEventArgs(TouchHandler handler, ref WinApi.GESTUREINFO gestureInfo) { _Flags = gestureInfo.dwFlags; GestureId = gestureInfo.dwID; GestureArguments = gestureInfo.ullArguments; //Get the last event from the handler LastEvent = handler.LastEventArgs; //Get the last begin event from the handler LastBeginEvent = handler.LastBeginEventArgs; ParseGesture(handler.ParentControl, ref gestureInfo); //new gesture, clear last and first event fields if (IsBegin) { LastBeginEvent = null; LastEvent = null; } } //Decode the gesture private void ParseGesture(Control parentControl, ref WinApi.GESTUREINFO gestureInfo) { Location = parentControl.PointToClient(new Point(gestureInfo.ptsLocation.x, gestureInfo.ptsLocation.y)); Center = Location; switch (GestureId) { case WinApi.GID_ROTATE: ushort lastArguments = (ushort)(IsBegin ? 0 : LastEvent.GestureArguments); RotateAngle = WinApi.GID_ROTATE_ANGLE_FROM_ARGUMENT((ushort)(gestureInfo.ullArguments - lastArguments)); break; case WinApi.GID_ZOOM: Point first = IsBegin ? Location : LastBeginEvent.Location; Center = new Point((Location.X + first.X) / 2, (Location.Y + first.Y) / 2); ZoomFactor = IsBegin ? 1 : (double)gestureInfo.ullArguments / LastEvent.GestureArguments; //DistanceBetweenFingers = WinApi.LoDWord(gestureInfo.ullArguments); break; case WinApi.GID_PAN: PanTranslation = IsBegin ? new Size(0, 0) : new Size(Location.X - LastEvent.Location.X, Location.Y - LastEvent.Location.Y); int panVelocity = WinApi.HiDWord((long)(gestureInfo.ullArguments)); PanVelocity = new Size(WinApi.LoWord(panVelocity), WinApi.HiWord(panVelocity)); //DistanceBetweenFingers = WinApi.LoDWord(gestureInfo.ullArguments); break; } } private uint _GestureId; public uint GestureId { get { return _GestureId; } private set { _GestureId = value; } } private ulong _GestureArguments; public ulong GestureArguments { get { return _GestureArguments; } private set { _GestureArguments = value; } } private Point _Location; /// /// The client location of gesture. /// public Point Location { get { return _Location; } private set { _Location = value; } } /// /// Is this the first event of a gesture. /// public bool IsBegin { get { return (_Flags & WinApi.GF_BEGIN) != 0; } } /// /// It this last event of a gesture. /// public bool IsEnd { get { return (_Flags & WinApi.GF_END) != 0; } } /// /// Has gesture triggered inertia. /// public bool IsInertia { get { return (_Flags & WinApi.GF_INERTIA) != 0; } } /// /// Gesture relative rotation angle for Rotate event. /// private double _RotateAngle; public double RotateAngle { get { return _RotateAngle; } private set { _RotateAngle = value; } } /// /// Indicates calculated gesture center. /// private Point _Center; public Point Center { get { return _Center; } private set { _Center = value; } } /// /// Gesture zoom factor for Zoom event. /// private double _ZoomFactor; public double ZoomFactor { get { return _ZoomFactor; } private set { _ZoomFactor = value; } } /// /// Gesture relative panning translation for Pan event. /// private Size _PanTranslation; public Size PanTranslation { get { return _PanTranslation; } set { _PanTranslation = value; } } /// /// Gesture velocity vector of the pan gesture for custom inertia implementations. /// private Size _PanVelocity; public Size PanVelocity { get { return _PanVelocity; } private set { _PanVelocity = value; } } private GestureEventArgs _LastBeginEvent; /// /// The first touch arguments in this gesture event sequence. /// public GestureEventArgs LastBeginEvent { get { return _LastBeginEvent; } internal set { _LastBeginEvent = value; } } /// /// The last touch arguments in this gesture event sequence. /// private GestureEventArgs _LastEvent; public GestureEventArgs LastEvent { get { return _LastEvent; } internal set { _LastEvent = value; } } } /// /// EventArgs passed to Touch handlers /// internal class TouchEventArgs : EventArgs { private readonly TouchHandler _ParentHandler; private readonly float _dpiXFactor; private readonly float _dpiYFactor; /// /// Create new touch event argument instance /// /// The target control /// one of the inner touch input in the message internal TouchEventArgs(TouchHandler parentHandler, float dpiX, float dpiY, ref WinApi.TOUCHINPUT touchInput) { _ParentHandler = parentHandler; _dpiXFactor = 96F / dpiX; _dpiYFactor = 96F / dpiY; DecodeTouch(ref touchInput); } private bool CheckFlag(int value) { return (Flags & value) != 0; } // Decodes and handles WM_TOUCH* messages. private void DecodeTouch(ref WinApi.TOUCHINPUT touchInput) { // TOUCHINFO point coordinates and contact size is in 1/100 of a pixel; convert it to pixels. // Also convert screen to client coordinates. if ((touchInput.dwMask & WinApi.TOUCHINPUTMASKF_CONTACTAREA) != 0) ContactSize = new Size(AdjustDpiX(touchInput.cyContact / 100), AdjustDpiY(touchInput.cyContact / 100)); Id = touchInput.dwID; Point p = _ParentHandler.ParentControl.PointToClient(new Point(touchInput.x / 100, touchInput.y / 100)); Location = p; // new Point(AdjustDpiX(p.X), AdjustDpiY(p.Y)); Time = touchInput.dwTime; TimeSpan ellapse = TimeSpan.FromMilliseconds(Environment.TickCount - touchInput.dwTime); AbsoluteTime = DateTime.Now - ellapse; Mask = touchInput.dwMask; Flags = touchInput.dwFlags; } private int AdjustDpiX(int value) { return (int)(value * _dpiXFactor); } private int AdjustDpiY(int value) { return (int)(value * _dpiYFactor); } /// /// Touch client coordinate in pixels /// private Point _Location; public Point Location { get { return _Location; } private set { _Location = value; } } /// /// A touch point identifier that distinguishes a particular touch input /// private int _Id; public int Id { get { return _Id; } private set { _Id = value; } } /// /// A set of bit flags that specify various aspects of touch point /// press, release, and motion. /// private int _Flags; public int Flags { get { return _Flags; } private set { _Flags = value; } } /// /// mask which fields in the structure are valid /// private int _Mask; public int Mask { get { return _Mask; } private set { _Mask = value; } } /// /// touch event time /// private DateTime _AbsoluteTime; public DateTime AbsoluteTime { get { return _AbsoluteTime; } private set { _AbsoluteTime = value; } } /// /// touch event time from system up /// private int _Time; public int Time { get { return _Time; } private set { _Time = value; } } /// /// the size of the contact area in pixels /// private Size? _ContactSize; public Size? ContactSize { get { return _ContactSize; } private set { _ContactSize = value; } } /// /// Is Primary Contact (The first touch sequence) /// public bool IsPrimaryContact { get { return (Flags & WinApi.TOUCHEVENTF_PRIMARY) != 0; } } /// /// Specifies that movement occurred /// public bool IsTouchMove { get { return CheckFlag(WinApi.TOUCHEVENTF_MOVE); } } /// /// Specifies that the corresponding touch point was established through a new contact /// public bool IsTouchDown { get { return CheckFlag(WinApi.TOUCHEVENTF_DOWN); } } /// /// Specifies that a touch point was removed /// public bool IsTouchUp { get { return CheckFlag(WinApi.TOUCHEVENTF_UP); } } /// /// Specifies that a touch point is in range /// public bool IsTouchInRange { get { return CheckFlag(WinApi.TOUCHEVENTF_INRANGE); } } /// /// specifies that this input was not coalesced. /// public bool IsTouchNoCoalesce { get { return CheckFlag(WinApi.TOUCHEVENTF_NOCOALESCE); } } /// /// Specifies that the touch point is associated with a pen contact /// public bool IsTouchPen { get { return CheckFlag(WinApi.TOUCHEVENTF_PEN); } } /// /// The touch event came from the user's palm /// /// Set to true public bool IsTouchPalm { get { return CheckFlag(WinApi.TOUCHEVENTF_PALM); } } } }