Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 1 )

Blogs

Mike Taulty's Blog

Elsewhere

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)pTouchInputsIdea.dwFlags;
         touchInput->X = pTouchInputsIdea.x;
         touchInput->Y = pTouchInputsIdea.y;
         touchInput->TouchId = pTouchInputsIdea.dwID;
         touchInput->TimestampInMilliseconds = pTouchInputsIdea.dwTime;
 
         if (pTouchInputsIdea.dwMask & TOUCHINPUTMASKF_CONTACTAREA)
         {
           touchInput->ContactWidth = pTouchInputsIdea.cxContact;
           touchInput->ContactHeight = pTouchInputsIdea.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.


Posted Thu, Jun 18 2009 2:41 AM by mtaulty
Filed under: ,

Comments

Mike Taulty's Blog wrote Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 2 )
on Thu, Jun 18 2009 2:49 AM

Following on from my previous post and remembering that I’m just playing around here and there are better

Dew Dump – June 18, 2009 | Alvin Ashcraft's Morning Dew wrote Dew Dump &#8211; June 18, 2009 | Alvin Ashcraft's Morning Dew
on Thu, Jun 18 2009 7:08 AM
Mike Taulty's Blog wrote Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 3 )
on Wed, Jul 1 2009 5:57 AM

Following on from the previous post and the one before , it was fun to play with my own interop wrappers

Mike Taulty's Blog wrote Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 5 )
on Mon, Jul 6 2009 7:52 AM

I got around to installing Visual Studio 2010 Beta 1 and so I thought it made sense to follow up this

NewsPeeps wrote Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 1 )
on Sat, Aug 8 2009 11:24 AM

Thank you for submitting this cool story - Trackback from NewsPeeps

代震军 wrote IT餐馆—第二十回 银光
on Wed, Oct 28 2009 9:45 PM

郑伟听雨辰话题一转,便正色道:“你说是silverlight3.0吧,我其本上通过SDK了解的差不多了,眼下我主要的兴趣还是在其视频的支持上,按其官方提供的技术文档说是新增了H.264,AAC,MP4格式支持。你呢?” 雨辰笑着说:“还是在其复杂数据展示上面,我想富媒体展示和交互是其主要方向,必定其对手是FLASH,换句话,FLASH中的优势应该被silverlight所吸取并完善。另外我们的产品中也使用了SL进行开发了。”