Share via


Exercise 1: Build a Multitouch Application

Task 1 – Prepare the Application

  1. Start Visual Studio 2010
  2. Create a new MFC application project and give it the name ScratchPad:

  1. In the Application Type, select Single Document. To keep the application simple, unselect the other options in the dialog similar to the screen-shots below:

  2. Continue clicking Next until you finally hit Finish:

Task 2 – Add Touch Support to the application

  1. The application that we are building requires touch-enabled hardware, so we need to make this check in the application.
  2. In Scratchpad.cpp, add the following check at the end of CScratchPadApp::InitInstance():BYTE digitizerStatus = (BYTE) GetSystemMetrics(SM_DIGITIZER);
    if ((digitizerStatus & (0x80 + 0x40)) == 0) //Stack Ready + MultiTouch
    {
    AfxMessageBox(L"No touch input is currently available.");
    return FALSE;
    }

    BYTE nInputs = (BYTE) GetSystemMetrics(SM_MAXIMUMTOUCHES);

    CString str;
    str.Format(L"Touch input available with %d touch points.", nInputs);
    AfxMessageBox(str);

    return TRUE;
  3. You can see that besides checking for touch availability and readiness we also find out the number of touch inputs that the hardware supports.
  4. Compile and run.
  5. Depending on the number of touch inputs you have on your machine, you should see output similar to this:

  6. In order to register the application client view window to receive Touch messages, we need to call the MFC function CWnd::RegisterTouchWindow(). We’ll do so after the view has been created; i.e. in the OnCreate() event handler.
  7. Switch to the Class View and Select the CChildView class.In the Properties page, go to the Message property sheet and navigate to WM_CREATE, then add the OnCreate() message handler from the drop down box:

  8. Inside the CChildView::OnCreate() handler append the following code to register touch input for the view window: if (!RegisterTouchWindow())
    {
    ASSERT(FALSE);
    }
    Note:
    Calling CWnd::RegisterTouchWindow() registers (and unregisters) a window as touch capable, allowing it to receive low-level WM_TOUCH messages.
  9. Since we’ve registered the view to receive touch input, we must override the handler receiving each of the touch messages: CWnd::OnTouchInput().
  10. This handler receives a single input from Windows Touch and should return TRUE if the application processes this message; otherwise FALSE.
  11. In ChildView.h, add this method declaration:// Overrides
    protected:
    virtual BOOL OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput);
  12. And in ChildView.cpp, provide the corresponding implementation:BOOL CChildView::OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput)
    {
    // TODO: Handle Tocuh input messages here
    return FALSE;
    }

Task 3 – Add the Stroke Source and Header Files to the Project, and Draw Lines with your Fingers

We would like to use our fingers as multiple input devices. We want to draw a line for each finger that touches the screen. To do that we are going to use two stroke collections. One collection holds the finished strokes (lines) and another collection holds the ongoing, currently painting lines. Each finger that touches the screen adds points to a stroke in the m_StrkColDrawing collection. When we raise the finger from the screen, we move the finger's stroke from the m_StrkColDrawing to the m_StrkColFinished collection. In addition we want strokes to have different colors if a user is using two or more fingers on a multitouch monitor.

  1. In the Starter folder you will find two files: Stroke.h and Stroke.cpp. Copy them to the project folder and use “Add Existing item…” to add them to the project.
  2. Similarly, add StrokeCollection.h and StrokeCollection.cpp to the project.
  3. Include "Stroke.h" and "StrokeCollection.h" at the end of StdAfx.h header file.#include "Stroke.h"
    #include "StrokeCollection.h"
  4. Add these private member variable definitions to ChildView.h:private:
    int m_iCurrColor; // The current stroke color
    CStrokeCollection m_StrkColFinished; // The user finished entering strokes // after user lifted his or her finger.
    CStrokeCollection m_StrkColDrawing; // The Strokes collection the user is // currently drawing.

  5. Important: we have to initialize the current color. We’ll do so in the CChildView constructor in ChildView.cpp:CChildView::CChildView() : m_iCurrColor(0)
    {
    }
  6. To draw the finished collection, we add the following call to the end of the CChildView::OnPaint() handler. It will draw all finished strokes.m_StrkColFinished.Draw(&dc);
  7. We need to process each Touch input message received, so we handle each of the events we’re interested in: touch input down, move and up.
  8. In CChildView.h, declare the following methods, which we’ll use to handle different touch input events:protected:
    // Handlers for different touch input events
    BOOL OnTouchInputDown(CPoint pt, PTOUCHINPUT pInput);
    BOOL OnTouchInputMove(CPoint pt, PTOUCHINPUT pInput);
    BOOL OnTouchInputUp(CPoint pt, PTOUCHINPUT pInput);
  9. In CChildView.cpp, add the implementation of each of the touch input handlers:BOOL CChildView::OnTouchInputDown(CPoint pt, PTOUCHINPUT pInput)
    {
    // Create new stroke and add point to it.
    COLORREF strokeColor = GetTouchColor((pInput->dwFlags & TOUCHEVENTF_PRIMARY) != 0);

    CStroke* pStrkNew = new CStroke(pInput->dwID, strokeColor);
    pStrkNew->Add(pt);

    // Add new stroke to the collection of strokes in drawing.
    m_StrkColDrawing.Add(pStrkNew);

    return TRUE;
    }

    BOOL CChildView::OnTouchInputMove(CPoint pt, PTOUCHINPUT pInput)
    {
    // Find the stroke in the collection of the strokes in drawing.
    int strokeIndex = m_StrkColDrawing.FindStrokeById(pInput->dwID);

    if (strokeIndex >= 0)
    {
    CStroke* pStrk = m_StrkColDrawing[strokeIndex];

    // Add contact point to the stroke
    pStrk->Add(pt);

    // Draw the last stroke
    pStrk->Draw(GetDC());
    }

    return TRUE;
    }

    BOOL CChildView::OnTouchInputUp(CPoint pt, PTOUCHINPUT pInput)
    {
    // Find the stroke in the collection of the strokes in drawing.
    int strokeIndex = m_StrkColDrawing.FindStrokeById(pInput->dwID);

    if (strokeIndex >= 0)
    {
    CStroke* pStrkCopy = m_StrkColDrawing[strokeIndex];

    // Remove this stroke from the collection of strokes in drawing.
    m_StrkColDrawing.RemoveAt(strokeIndex);

    // Add this stroke to the collection of finished strokes.
    m_StrkColFinished.Add(pStrkCopy);
    }

    return TRUE;
    }
  10. In CChildView.cpp, modify the implementation of CChildView::OnTouchInput() to call each of the touch input handlers as needed:BOOL CChildView::OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput)
    {
    if ((pInput->dwFlags & TOUCHEVENTF_DOWN) == TOUCHEVENTF_DOWN) // Touch Down event
    {
    return OnTouchInputDown(pt, pInput);
    }
    else if ((pInput->dwFlags & TOUCHEVENTF_MOVE) == TOUCHEVENTF_MOVE) // Touch Move event
    {
    return OnTouchInputMove(pt, pInput);
    }
    else if ((pInput->dwFlags & TOUCHEVENTF_UP) == TOUCHEVENTF_UP) // Touch Move event
    {
    return OnTouchInputUp(pt, pInput);
    }

    return FALSE;
    }
  11. Note that the method GetTouchColor() is called but is not yet implemented. This method is responsible for changing the color of the pen when a user moves more than one finger on the app window. Add this method’s declaration in CChildView.h:private:
    COLORREF GetTouchColor(bool bPrimaryContact);
  12. And here’s the implementation in CChildView.cpp:COLORREF CChildView::GetTouchColor(bool bPrimaryContact)
    {
    static COLORREF c_arrColor[] = // Secondary colors array
    {
    RGB(255, 0, 0), // Red
    RGB(0, 255, 0), // Green
    RGB(0, 0, 255), // Blue
    RGB(0, 255, 255), // Cyan
    RGB(255, 0, 255), // Magenta
    RGB(255, 255, 0) // Yellow
    };

    COLORREF color;
    if (bPrimaryContact)
    {
    // The primary contact is drawn in black.
    color = RGB(0,0,0); // Black
    }
    else
    {
    // Take current secondary color.
    color = c_arrColor[m_iCurrColor];

    // Move to the next color in the array.
    m_iCurrColor = (m_iCurrColor + 1) % (sizeof(c_arrColor)/sizeof(c_arrColor[0]));
    }

    return color;
    }
  13. Finally, since we’ve dynamically created a number of strokes, we need to make sure each of those is destroyed before the application exits, so we include the following in the CChildView‘s destructor:CChildView::~CChildView()
    {
    for (int i = 0; i < m_StrkColDrawing.GetCount(); ++i)
    {
    delete m_StrkColDrawing[i];
    }

    for (int i = 0; i < m_StrkColFinished.GetCount(); ++i)
    {
    delete m_StrkColFinished[i];
    }
    }
  14. You’re now done with coding, so you can start experimenting with the application you’ve just implemented.
  15. Build and run the application. It should look like this: