//----------------------------------------------------------------------------- // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using Windows7.Multitouch.Interop; using System.Reflection; using System.Threading; using System.Drawing; namespace Windows7.Multitouch { /// /// Wrapp HWND source such as System.Windows.Forms.Control, or System.Windows.Window /// interface IHwndWrapper { /// /// The Underline Windows Handle /// IntPtr Handle { get; } /// /// The .NET object that wrap the Windows Handle (WinForm, WinForm Control, WPF Window, IntPtr of HWND) /// object Source { get; } /// /// The Win32 Handle has been created /// event EventHandler HandleCreated; /// /// /// The Win32 Handle has been destroyed /// event EventHandler HandleDestroyed; /// /// Check if the Win32 Handle is already created /// bool IsHandleCreated { get; } /// /// Computes the location of the specified screen point into client coordinates /// /// The screen coordinate System.Drawing.Point to convert /// A point that represents the converted point in client coordinates Point PointToClient(Point point); } /// /// A Common interface foir timer. /// The timer has to be in the UI thread context /// public interface IGUITimer : IDisposable { /// /// Gets or sets whether the timer is running. /// bool Enabled { get; set; } /// /// Gets or sets the time, in milliseconds, before the Tick event is raised /// int Interval { get; set; } /// /// Occurs when the specified timer interval has elapsed and the timer is enabled. /// event EventHandler Tick; /// /// Starts the timer. /// void Start(); /// /// Stops the timer. /// void Stop(); } /// /// Base class for handling Gesture and Touch event /// /// /// A form can have one handler, either touch handler or gesture handler. /// The form need to create the handler and register to events. /// The code is not thread safe, we assume that the calling thread is the /// UI thread. There is no blocking operation in the code. /// public abstract class Handler { private readonly IHwndWrapper _hWndWrapper; static private List _controlInUse = new List(); private User32.WindowProcDelegate _windowProcDelegate; private IntPtr _originalWindowProcId; /// /// Initiate touch support for the underline hWnd /// /// Registering the hWnd to touch support or configure the hWnd to receive gesture messages /// true if succeeded protected abstract bool SetHWndTouchInfo(); /// /// The interceptor WndProc /// /// WndProc hWnd /// WndProc msg /// WndProc wParam /// WndProc lPara /// WndProc return protected abstract uint WindowProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam); internal static T CreateHandler(IHwndWrapper hWndWrapper) where T : Handler { if (_controlInUse.Contains(hWndWrapper.Source)) throw new Exception("Only one handler can be registered for a control."); hWndWrapper.HandleDestroyed += (s, e) => _controlInUse.Remove(s); _controlInUse.Add(hWndWrapper.Source); return Activator.CreateInstance(typeof(T), BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance, null, new object [] { hWndWrapper }, Thread.CurrentThread.CurrentCulture) as T; } /// /// We create the hanlder using a factory method. /// /// The control or Window that registered for touch/gesture events internal Handler(IHwndWrapper hWndWrapper) { _hWndWrapper = hWndWrapper; if (_hWndWrapper.IsHandleCreated) Initialize(); else _hWndWrapper.HandleCreated += (s, e) => Initialize(); } /// /// Connect the handler to the Control /// /// /// The trick is to subclass the Control and intercept touch/gesture events, then reflect /// them back to the control. /// private void Initialize() { if (!SetHWndTouchInfo()) { throw new NotSupportedException("Cannot register window"); } _windowProcDelegate = WindowProcSubClass; //According to the SDK doc SetWindowLongPtr should be exported by both 32/64 bit O/S //But it does not. _originalWindowProcId = IntPtr.Size == 4 ? User32.SubclassWindow(_hWndWrapper.Handle, User32.GWLP_WNDPROC, _windowProcDelegate) : User32.SubclassWindow64(_hWndWrapper.Handle, User32.GWLP_WNDPROC, _windowProcDelegate); //take the desktop DPI using (Graphics graphics = Graphics.FromHwnd(_hWndWrapper.Handle)) { DpiX = graphics.DpiX; DpiY = graphics.DpiY; } WindowMessage += (s, e) => { }; } /// /// Intercept touch/gesture events using Windows subclassing /// /// The hWnd of the registered form /// The WM code /// The WM WParam /// The WM LParam /// private uint WindowProcSubClass(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) { WindowMessage(this, new WMEventArgs(hWnd, msg, wparam, lparam)); if (msg == User32.WM_GESTURENOTIFY && GestureNotify != null) { GestureNotify(this, new GestureNotifyEventArgs(lparam)); } else { uint result = WindowProc(hWnd, msg, wparam, lparam); if (result != 0) return result; } return User32.CallWindowProc(_originalWindowProcId, hWnd, msg, wparam, lparam); } /// /// The registered control wrapper /// internal IHwndWrapper HWndWrapper { get { return _hWndWrapper; } } /// /// The registered control's handler /// protected IntPtr ControlHandle { get { return _hWndWrapper.IsHandleCreated ? _hWndWrapper.Handle : IntPtr.Zero; } } /// /// The X DPI of the target window /// public float DpiX { get; private set; } /// /// The Y DPI of the target window /// public float DpiY { get; private set; } /// /// GestureNotify event notifies a window that gesture recognition is // in progress and a gesture will be generated if one is recognized under the // current gesture settings. /// public event EventHandler GestureNotify; /// /// Enable advanced message handling/blocking /// internal event EventHandler WindowMessage; /// /// Report digitizer capabilities /// public static class DigitizerCapabilities { /// /// Get the current Digitizer Status /// public static DigitizerStatus Status { get { return (DigitizerStatus)User32.GetDigitizerCapabilities(User32.DigitizerIndex.SM_DIGITIZER); } } /// /// Get the maximum touches capability /// public static int MaxumumTouches { get { return User32.GetDigitizerCapabilities(User32.DigitizerIndex.SM_MAXIMUMTOUCHES); } } /// /// Check for integrated touch support /// public static bool IsIntegratedTouch { get { return (Status & DigitizerStatus.IntegratedTouch) != 0; } } /// /// Check for external touch support /// public static bool IsExternalTouch { get { return (Status & DigitizerStatus.ExternalTouch) != 0; } } /// /// Check for Pen support /// public static bool IsIntegratedPan { get { return (Status & DigitizerStatus.IntegratedPan) != 0; } } /// /// Check for external Pan support /// public static bool IsExternalPan { get { return (Status & DigitizerStatus.ExternalPan) != 0; } } /// /// Check for multi-input /// public static bool IsMultiInput { get { return (Status & DigitizerStatus.MultiInput) != 0; } } /// /// Check if touch device is ready /// public static bool IsStackReady { get { return (Status & DigitizerStatus.StackReady) != 0; } } /// /// Check if Multi-touch support device is ready /// public static bool IsMultiTouchReady { get { return (Status & (DigitizerStatus.StackReady | DigitizerStatus.MultiInput)) != 0; } } } /// /// Check if the Window is registered for multitouch events /// /// /// public static bool IsTouchWindows(IntPtr hWnd) { uint cap; return User32.IsTouchWindow(hWnd, out cap); } } /// /// GestureNotify event notifies a window that gesture recognition is // in progress and a gesture will be generated if one is recognized under the // current gesture settings. /// public class GestureNotifyEventArgs : EventArgs { internal GestureNotifyEventArgs(IntPtr lparam) { User32.GESTURENOTIFYSTRUCT gestureNotify = (User32.GESTURENOTIFYSTRUCT)Marshal.PtrToStructure(lparam, typeof(User32.GESTURENOTIFYSTRUCT)); //TODO: Do we need to be DPI aware also here? Location = new Point(gestureNotify.ptsLocation.x, gestureNotify.ptsLocation.y); TargetHwnd = gestureNotify.hwndTarget; } /// /// The gesture location /// public Point Location { get; private set; } /// /// The gesture target window /// public IntPtr TargetHwnd { get; private set; } } /// /// Enable advanced message handling/blocking /// internal class WMEventArgs : EventArgs { public WMEventArgs(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) { HWnd = hWnd; Message = msg; WParam = wparam; LParam = lparam; } public IntPtr HWnd { get; private set; } public int Message { get; private set; } public IntPtr WParam { get; private set; } public IntPtr LParam { get; private set; } } #pragma warning disable 1591 /// /// All availible digitizer capabilities /// [Flags] public enum DigitizerStatus : byte { IntegratedTouch = 0x01, ExternalTouch = 0x02, IntegratedPan = 0x04, ExternalPan = 0x08, MultiInput = 0x40, StackReady = 0x80 } }