Beispiel für Manipulation und Trägheit

Das Beispiel für Manipulation und Trägheit zeigt, wie Windows Touch-Unterstützung zu systemeigenen Windows-basierten Anwendungen hinzugefügt wird, die die Windows Touch-API verwenden. Im Beispiel werden die grundlegenden Features der API implementiert, um Übersetzung, Drehung und Skalierung für Objekte zu ermöglichen und sie mit Trägheitseigenschaften zu versehen. Das Beispiel zeigt auch, wie Sie Ihren Windows Touch-Anwendungen grundlegende Mausunterstützung geben. Die folgende Abbildung zeigt, wie das Beispiel aussieht, wenn es ausgeführt wird.

Screenshot mit zwei Feldern mit Farbverläufen im Manipulations- und Trägheits-Beispiel

Die Felder mit Farbverläufen können von einem Benutzer unabhängig bearbeitet werden, wenn die Anwendung von einem Computer ausgeführt wird, der Windows Touch unterstützt.

Registrieren des Touch-Fensters

Bevor Sie Toucheingaben empfangen können, müssen Sie zuerst das System benachrichtigen, dass Ihre Anwendung eine Windows Touch-Anwendung ist, indem Sie die folgende Funktion aufrufen:

   RegisterTouchWindow(g_hWnd, 0);

Implementieren der _IManipulationEventSink-Schnittstelle

Die _IManipulationEvents-Ereignissenke enthält drei Funktionen: ManipulationStarted, ManipulationDelta und ManipulationCompleted. Diese Rückruffunktionen werden von der IManipulationProcessor-Schnittstelle und der IInertiaProcessor-Schnittstelle für die Rückgabe der von den Prozessoren errechneten Werte verwendet, nachdem die Funktionen ProcessTime, ProcessUpWithTime, ProcessDownWithTime und ProcessMoveWithTime aufgerufen wurden. Das folgende Codebeispiel zeigt eine Beispielimplementierung einer _IManipulationEvents-Schnittstelle.

#include "cmanipulationeventsink.h"
#include <math.h>

CManipulationEventSink::CManipulationEventSink(HWND hWnd, CDrawingObject *dObj, int iTimerId, BOOL inertia) {
    // Manipulation & Inertia Processors
    m_manip = NULL;
    m_inert = NULL;

    // Connection points for COM.
    m_pConPointContainer = NULL;
    m_pConnPoint = NULL;

    // Reference to an object associated with this event sink.
    m_dObj = dObj;

    // Handle to the window used for computing boundaries.
    m_hWnd = hWnd;

    // The unique timer id for this manipulation event sink.
    m_iTimerId = iTimerId;

    m_bInertia = inertia;

    m_cRefCount = 1;
}


CManipulationEventSink::~CManipulationEventSink()
{

}


HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationStarted(
    FLOAT x,
    FLOAT y)
{
    KillTimer(m_hWnd, m_iTimerId);

    return S_OK;
}

HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationDelta(
    FLOAT x,
    FLOAT y,
    FLOAT translationDeltaX,
    FLOAT translationDeltaY,
    FLOAT scaleDelta,
    FLOAT expansionDelta,
    FLOAT rotationDelta,
    FLOAT cumulativeTranslationX,
    FLOAT cumulativeTranslationY,
    FLOAT cumulativeScale,
    FLOAT cumulativeExpansion,
    FLOAT cumulativeRotation)
{
    FLOAT pivot = 0.0f;

    // Apply transformation based on rotationDelta (in radians).
    FLOAT rads = 180.0f / 3.14159f;
    m_dObj->Rotate(rotationDelta*rads, x, y);

    // Apply translation based on scaleDelta.
    m_dObj->Scale(scaleDelta);

    // Apply translation based on translationDelta.
    m_dObj->Translate(translationDeltaX, translationDeltaY);

    if(!m_bInertia)
    {
        // Set values for one-finger rotations.
        FLOAT fPivotRadius = (FLOAT)(sqrt(pow(m_dObj->GetWidth()/2, 2)
                           + pow(m_dObj->GetHeight()/2, 2)))*0.4f;
        FLOAT fPivotPtX    = m_dObj->GetCenterX();
        FLOAT fPivotPtY    = m_dObj->GetCenterY();

        m_manip->put_PivotPointX(fPivotPtX);
        m_manip->put_PivotPointY(fPivotPtY);
        m_manip->put_PivotRadius(fPivotRadius);
    }
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationCompleted(
    FLOAT x,
    FLOAT y,
    FLOAT cumulativeTranslationX,
    FLOAT cumulativeTranslationY,
    FLOAT cumulativeScale,
    FLOAT cumulativeExpansion,
    FLOAT cumulativeRotation)
{
    if(!m_bInertia)
    {
        SetupInertia();

        // Kick off timer that handles inertia.
        SetTimer(m_hWnd, m_iTimerId, DESIRED_MILLISECONDS, NULL);
    }
    else
    {
        // Stop timer that handles inertia.
        KillTimer(m_hWnd, m_iTimerId);
    }

    return S_OK;
}

Erstellen von COM-Objekten und Einrichten der IManipulationProcessor- und IInertiaProcessor-Schnittstellen

Die API stellt eine Implementierung der Schnittstellen IManipulationProcessor und IInertiaProcessor bereit. Sie sollten eine Instanz der COM-Objekte erstellen und aus der zuvor implementierten IManipulationEvents-Ereignissenke darauf verweisen.

Umgang mit WM_TOUCH-Nachrichten

Die Eingabedaten müssen aus den WM_TOUCH-Nachrichten extrahiert und später verarbeitet werden, um den korrekten Bearbeitungsprozessor zu beschicken.

switch (msg)
{
    case WM_TOUCH:

      iNumContacts = LOWORD(wParam);
      hInput       = (HTOUCHINPUT)lParam;
      pInputs      = new TOUCHINPUT[iNumContacts];

      // Get each touch input info and feed each
      // tagTOUCHINPUT into the process input handler.

      if(pInputs != NULL)
      {
          if(GetTouchInputInfo(hInput, iNumContacts,
               pInputs, sizeof(TOUCHINPUT)))
          {
              for(int i = 0; i < iNumContacts; i++)
              {
                  // Bring touch input info into client coordinates.
                  ptInputs.x = pInputs[i].x/100;
                  ptInputs.y = pInputs[i].y/100;
                  ScreenToClient(g_hWnd, &ptInputs);

                  pInputs[i].x = ptInputs.x;
                  pInputs[i].y = ptInputs.y;

                  g_ctDriver->ProcessInputEvent(pInputs[i]);
              }
          }
      }

      delete [] pInputs;
      break;
}

Hinweis

Um die ScreenToClient-Funktion zu verwenden, müssen Sie in Ihrer Anwendung über Unterstützung für hohe DPI-Werte verfügen. Weitere Informationen zur Unterstützung hoher DPI-Werte finden Sie unter Hoher DPI-Wert.

Übergeben von TOUCHINPUT-Strukturen an den entsprechenden Prozessor

Nachdem die Daten aus den WM_TOUCH-Nachrichten mithilfe der GetTouchInputInfo-Funktion extrahiert wurden, geben Sie die Daten in den Manipulationsprozessor, indem Sie die Funktionen ProcessUpWithTime, ProcessDownWithTime oder ProcessMoveWithTime aufrufen, je nach der dwFlag in der TOUCHINPUT-Struktur.

Hinweis

Wenn mehrere Manipulationen unterstützt werden, muss ein neuer Manipulationsprozessor erstellt werden, wenn die in der TOUCHINPUT-Struktur definierte dwID verwendet werden muss, um die Daten an das richtige IManipulationProcessor-Objekt zu senden.

CoreObject* coCurrent = m_coHead;

while(coCurrent!=NULL && !bFoundObj)
{
    if(dwEvent & TOUCHEVENTF_DOWN)
    {
        DownEvent(coCurrent, inData, &bFoundObj);
    }
    else if(dwEvent & TOUCHEVENTF_MOVE)
    {
        MoveEvent(coCurrent, inData);
    }
    else if(dwEvent & TOUCHEVENTF_UP)
    {
        UpEvent(coCurrent, inData);
    }
    coCurrent = coCurrent->coNext;
}

VOID CComTouchDriver::DownEvent(CoreObject* coRef, tagTOUCHINPUT inData, BOOL* bFound) {
    DWORD dwPCursor = inData.dwID;
    DWORD dwPTime   = inData.dwTime;
    int x           = inData.x;
    int y           = inData.y;

    // Check that the user has touched within an object's region and fed to the object's manipulation processor.

    if(coRef->doDrawing->InRegion(x, y) &&
        !HasCursor(coRef, dwPCursor))
    {
        ...

        // Feed values to the Manipulation Processor.
        coRef->manipulationProc->ProcessDownWithTime(dwPCursor, (FLOAT)x, (FLOAT)y, dwPTime);

        ...
    }
}

Einrichten der Trägheit in ManipulationCompleted

Nachdem die ManipulationCompleted-Methode aufgerufen wurde, muss das IManipulationProcessor-Objekt die Werte für das IInertiaProcessor-Objekt setzen, das mit dem IManipulationProcessor verknüpft ist, um Trägheit aufzurufen. Das folgende Codebeispiel zeigt, wie das IInertiaProcessor-Objekt aus der IManipulationProcessor-Methode ManipulationCompleted eingerichtet wird.

    int iVWidth       = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    int iVHeight      = GetSystemMetrics(SM_CYVIRTUALSCREEN);

    RECT rc;
    GetClientRect(m_hWnd, &rc);
    FLOAT lCWidth     = (FLOAT)rc.right;
    FLOAT lCHeight    = (FLOAT)rc.bottom;

    // Set properties for inertia events.

    // Deceleration for tranlations in pixel / msec^2.
    m_inert->put_DesiredDeceleration(0.001f);

    // Deceleration for rotations in radians / msec^2.
    m_inert->put_DesiredAngularDeceleration(0.00001f);

    // Calculate borders and elastic margin to be set.
    // They are relative to the width and height of the object.

    FLOAT fHOffset = m_dObj->GetWidth()  * 0.5f;
    FLOAT fVOffset = m_dObj->GetHeight() * 0.5f;

    // Elastic margin is in pixels - note that it offsets the boundary.

    FLOAT fHElasticMargin = 25.0f;
    FLOAT fVElasticMargin = 25.0f;

    FLOAT fBoundaryLeft   = fHOffset + fHElasticMargin;
    FLOAT fBoundaryTop    = fVOffset + fVElasticMargin;
    FLOAT fBoundaryRight  = lCWidth - fHOffset - fHElasticMargin;
    FLOAT fBoundaryBottom = lCHeight - fVOffset - fVElasticMargin;

    // Set borders and elastic margin.

    m_inert->put_BoundaryLeft(fBoundaryLeft);
    m_inert->put_BoundaryTop(fBoundaryTop);
    m_inert->put_BoundaryRight(fBoundaryRight);
    m_inert->put_BoundaryBottom(fBoundaryBottom);

    m_inert->put_ElasticMarginLeft(fHElasticMargin);
    m_inert->put_ElasticMarginTop(fVElasticMargin);
    m_inert->put_ElasticMarginRight(fHElasticMargin);
    m_inert->put_ElasticMarginBottom(fVElasticMargin);

    // Set initial origins.

    m_inert->put_InitialOriginX(m_dObj->GetCenterX());
    m_inert->put_InitialOriginY(m_dObj->GetCenterY());

    FLOAT fVX;
    FLOAT fVY;
    FLOAT fVR;

    m_manip->GetVelocityX(&fVX);
    m_manip->GetVelocityY(&fVY);
    m_manip->GetAngularVelocity(&fVR);

    // Set initial velocities for inertia processor.

    m_inert->put_InitialVelocityX(fVX);
    m_inert->put_InitialVelocityY(fVY);
    m_inert->put_InitialAngularVelocity(fVR);

Bereinigen Ihrer COM-Objekte

Wenn Ihre Anwendung geschlossen wird, müssen Sie Ihre COM-Objekte bereinigen. Der folgende Code zeigt, wie Sie die Ressourcen freigeben können, die im Beispiel zugeordnet wurden.

CComTouchDriver::~CComTouchDriver(VOID) {
    CoreObject* coCurrent = m_coHead;

    // Clean up COM objects.

    while(coCurrent!=NULL)
    {
        coCurrent->inertiaEventSink->Release();
        coCurrent->manipulationEventSink->Release();
        coCurrent->inertiaProc->Release();
        coCurrent->manipulationProc->Release();
        coCurrent = coCurrent->coNext;
    }
}

Multitouch Manipulation Application, Beispiel für Manipulation und Trägheit, Windows Touch-Beispiele