Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 1 )

I was down in the Microsoft Technology Centre at Reading the other week where I got a chance to take a look at a piece of proof-of-concept work that one of our customers is doing around using touch in Windows 7 to improve the user experience of one of their applications.

( I’ve captured this on video and will share at a later point as I’m not able to just at the moment ).

But this got me thinking about Multi-Touch in Windows 7 and wondering how easy/hard it is.

I’ve done some work with the Tablet PC in my time and that wasn’t too tricky and I gave an example of that at DevDays the other week in that I used a simple WPF application that handles basic ink gestures;

image

and that’s just using a WPF InkCanvas with its gesture mode set to GestureOnly and a little code behind it ( download the source here if you like ).

So, that was easy enough but what about Multi-Touch? Well, the first “problem” I have is that I don’t have any multi-touch-enabled hardware. I’ve seen 2 multi-touch-enabled machines, the HP TouchSmart;

and the Dell XT2;

neither of which I own and neither of which is Microsoft about to buy me 🙂

So, I fired up the Windows 7 SDK and wrote a little code in order to experiment.

BIG NOTE: this is not really the right way to go. The right way to go is either to look at WPF V4.0 which I’ll return to or to head to;

http://code.msdn.microsoft.com/WindowsTouch

which will point you to;

http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=WindowsTouch&DownloadId=4525

and there you can download a whole bunch of interop wrappers that’ll make your life a lot easier. I’ll return to those too in a subsequent post but, initially, I just wanted to have a glance at the underlying API’s a little first and that’s what follows here and on the next post.

I pretty soon realised that my Windows 7 SDK was out of date ( the previous link is ok though ) and so I flicked through the online docs instead whilst downloading the updated ISO image for the RC rather than the beta.

From what I could tell, there’s a new parameter value for GetSystemMetrics which can tell you what kind of touch capabilities the device you are on has and so I figured I’d write a little class to surface that;

static class TouchCapabilities
  {
    // Taken from the Windows 7 SDK, possible transcription errors.
    [Flags]
    private enum TouchMetrics
    {
      IntegratedTouch = 0x1,
      ExternalTouch = 0x2,
      IntegratedPen = 0x4,
      ExternalPen = 0x8,
      MultiInput = 0x40,
      StackReady = 0x80
    }

    public static bool HasMultiTouch
    {
      get
      {
        return (
          (((TouchMetrics)GetSystemMetrics(SM_DIGITIZER)) &
            TouchMetrics.MultiInput) != 0);
      }
    }
    const int SM_DIGITIZER = 94;

    // Windows 7 SDK says that a failure here ( 0 ) will not set LastError.
    [DllImport("User32")]
    private static extern int GetSystemMetrics(int nIndex);
  }

I dropped that into a Windows Forms application, called it and came up with the somewhat-expected response of false.

So things were looking a little “tricky” until Paul pointed me at a CodePlex project that helps out with a virtual driver so I downloaded those bits, watched the installation video and installed the bits and rebooted. I also added in a second mouse to use as my second touch input.

Then when I run my application I see a decent return value from my TouchCapabilities.HasMultiTouch property.

Ok…maybe this gets me somewhere? When you look at the SDK there’s a few sides to the touch capabilities;

  • New windows messages (WM_TOUCH) representing touch up/down/move with identification of multiple touch points
  • New windows messages (WM_GESTURE) representing multi-touch gesture messages
  • Manipulation support – from a set of ( from manipulations.idl ) translateX, translateY, scale, rotate
  • Inertia support – there’s not much in the docs here so I think a bit of experimentation would be called for

So, having asked whether I’m on a touch-enabled device or not, maybe I can capture some touch events? I wrote a little Panel-derived class ( Windows Forms Panel that is ) to try and pick some up – you need to call RegisterTouchWindow only if you want the lower level touch messages rather than the gesture messages;

class TouchPanel : Panel
 {
   public bool Register()
   {
     return (RegisterTouchWindow(this.Handle, 0));
   }
   public bool Unregister()
   {
     return (UnregisterTouchWindow(this.Handle));
   }
   protected override void DefWndProc(ref Message m)
   {
     if (m.Msg == WM_TOUCH)
     {
       Debug.WriteLine("Got a touch message!");
     }
     else
     {
       base.DefWndProc(ref m);
     }
   }
   [DllImport("User32")]
   private static extern bool RegisterTouchWindow(IntPtr handle, UInt32 flags);

   [DllImport("User32")]
   private static extern bool UnregisterTouchWindow(IntPtr handle);

   const UInt32 TWF_FINETOUCH = 1;

   // From Winuser.h
   const int WM_TOUCH = 0x240;
   const int WM_GESTURE = 0x119;
 }

and that all seemed to go well enough in that I saw my Debug.WriteLine calls executing. To decipher what’s actually present in a WM_TOUCH message you have to pass it to GetTouchInputInfo and I didn’t fancy writing the interop classes to make that work so I wrote a bit of C++/CLI to do it for me which looks a bit like this (header file);

namespace NativeCodeHelpers {

  [Flags]
  public enum class TouchInputType
  {
    Down      = 0x01,
    InRange  = 0x08,
    Move      = 0x02,
    NoCoalese = 0x20,
    Palm      = 0x80,
    Primary  = 0x10,
    Up        = 0x4
  };

  public ref class TouchInput
  {
  public:
    property TouchInputType InputType;
    property long X;
    property long Y;
    property UInt32 TouchId;
    property UInt32 TimestampInMilliseconds;
    property Nullable<UInt32> ContactWidth;
    property Nullable<UInt32> ContactHeight;

    virtual String^ ToString() override;

  internal:

    TouchInput();
  };

  public ref class TouchInputDecoder
  {
  public:
    TouchInputDecoder(IntPtr wParam, IntPtr lParam);

    property List<TouchInput^>^ Inputs
    {
      List<TouchInput^>^  get();
    };

  private:

    List<TouchInput^>^ _inputs;
  };
}

 

and the implementation file;

 
#include "stdafx.h"

 using namespace System;
 using namespace System::Collections::Generic;

 #include "NativeCodeHelpers.h"

 using namespace NativeCodeHelpers;

 TouchInput::TouchInput()
 {
 }

 
 String^ TouchInput::ToString()
 {
   return(String::Format(L"Touch Id {0}, Input Type {1}, X {2}, Y {3}, Timestamp {4}",
     this->TouchId, this->InputType, this->X, this->Y, this->TimestampInMilliseconds));
 }
 
 TouchInputDecoder::TouchInputDecoder(IntPtr wParam, IntPtr lParam)
 {
   _inputs = gcnew List<TouchInput^>();

   HTOUCHINPUT hTouchInput = (HTOUCHINPUT)lParam.ToPointer();
   UINT cInputs = (UINT)wParam.ToInt32();

   bool bThrow = false;

   if (cInputs)
   {
     TOUCHINPUT* pTouchInputs = new TOUCHINPUT[cInputs];
     ZeroMemory(pTouchInputs, cInputs * sizeof(*pTouchInputs));    
 
     if (GetTouchInputInfo(hTouchInput, cInputs, pTouchInputs, sizeof(*pTouchInputs)))
     {
       for (int i = 0; i < cInputs; i++)
       {
         TouchInput^ touchInput = gcnew TouchInput();
         touchInput->InputType = (TouchInputType)pTouchInputs.dwFlags;
         touchInput->X = pTouchInputs.x;
         touchInput->Y = pTouchInputs.y;
         touchInput->TouchId = pTouchInputs.dwID;
         touchInput->TimestampInMilliseconds = pTouchInputs.dwTime;
 
         if (pTouchInputs.dwMask & TOUCHINPUTMASKF_CONTACTAREA)
         {
           touchInput->ContactWidth = pTouchInputs.cxContact;
           touchInput->ContactHeight = pTouchInputs.cyContact;
         }
         _inputs->Add(touchInput);
       }
     }
 
     delete [] pTouchInputs;
     pTouchInputs = NULL;
   }
   ::CloseTouchInputHandle(hTouchInput);
 
   if (bThrow)
   {
     // TODO: Not a great exception to throw perhaps
     throw gcnew InvalidOperationException(L"Failed to get touch input information");
   }
 }

 
 List<TouchInput^>^ TouchInputDecoder::Inputs::get()
 {
   return(_inputs);
 }

with some actual code ( I tend not to write my code in header files, old habits die hard );

Ok, so with that in place I can write a Windows Form that uses my TouchPanel and this TouchInputDecoder class to get something basic done;
 
public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }
    private void OnRegister(object sender, EventArgs args)
    {
      if (TouchCapabilities.HasMultiTouch)
      {
        panel = new TouchPanel();

        panel.TouchInputReceived += (s, e) =>
          {
            foreach (TouchInput input in e.TouchInput)
            {
              txtDebugOutput.Text += string.Format("{0}{1}", input.ToString(),
                Environment.NewLine);
              txtDebugOutput.ScrollToCaret();
            }
          };

        if (panel.Register())
        {
          panel.BackColor = Color.SkyBlue;
          panel.Dock = DockStyle.Fill;
          this.Controls.Add(panel);
        }
        else
        {
          MessageBox.Show("Failed to register for touch input");
        }
      }
      else
      {
        MessageBox.Show("No multi touch input detected");
      }
    }
    private void OnUnregister(object sender, EventArgs e)
    {
      if (panel != null)
      {
        bool retVal = panel.Unregister();
        Debug.Assert(retVal);
      }
    }
    TouchPanel panel;
  }

and that worked out reasonably well for me in that I can run my application with the CodePlex driver, click the button that calls OnRegister in the code-behind above and then do a bit of “touch” input and get some debug tracing dumped into a TextBox;

image

It’s not a very exciting app but the blue bit at the bottom is the TouchPanel and the diagnostics are going into the TextBox above.

For the longest time, I was convinced that I only saw a single Touch Id ( value of 10 ) – but a judicious use of a conditional breakpoint in Visual Studio told me that there were times when I got 2 inputs simultaneously and that I had Touch Id 10 and Touch Id 100 both in flight at the same time.

So, I can pick up raw touch data but that all seems a bit low level ( it’s always where I like to start ) so it’d be nice to get some gestures instead I think….next post…but in the meantime the source for what I built is here for download if you want to play with it ( apply large pinch of salt, I’m just learning  ). As is probably clear, you’ll need the C++ bits installed to compile what I did here.