다음을 통해 공유


조작 및 관성 샘플

조작 및 관성 샘플은 Windows Touch API를 사용하는 네이티브 Windows 기반 애플리케이션에 Windows Touch 지원을 추가하는 방법을 보여 줍니다. 이 샘플은 개체에 대한 변환, 회전 및 크기 조정을 사용하도록 설정하고 관성 속성을 적용하는 API의 기본 기능을 구현합니다. 이 샘플에서는 Windows Touch 애플리케이션에 기본 마우스 지원을 제공하는 방법도 보여 줍니다. 다음 이미지는 샘플이 실행되면 어떻게 보이는지 보여줍니다.

조작 및 관성 샘플에서 그라데이션이 있는 두 개의 상자를 보여 주는 스크린샷

사용자가 Windows Touch를 지원하는 컴퓨터에서 애플리케이션을 실행할 때 그라데이션이 있는 상자는 독립적으로 조작할 수 있습니다.

터치 창 등록

터치 입력을 받으려면 먼저 다음 함수를 호출하여 애플리케이션이 Windows Touch 애플리케이션임을 시스템에 알려야 합니다.

   RegisterTouchWindow(g_hWnd, 0);

_IManipulationEventSink 인터페이스 구현

_IManipulationEvents 이벤트 싱크에는 ManipulationStarted, ManipulationDelta 및 ManipulationCompleted의 세 가지 함수가 포함되어 있습니다. 이러한 콜백 함수는 IManipulationProcessor 인터페이스 및 IInertiaProcessor 인터페이스에서 ProcessTime, ProcessUpWithTime, ProcessDownWithTimeProcessMoveWithTime 함수를 호출한 후 프로세서에서 계산한 값을 반환하는 데 사용됩니다. 다음 코드 예제에서는 _IManipulationEvents 인터페이스의 구현 예제를 보여 있습니다.

#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;
}

COM 개체 만들기 및 IManipulationProcessor 및 IInertiaProcessor 인터페이스 설정

API는 IManipulationProcessor IInertiaProcessor 인터페이스의 구현을 제공합니다. 이전에 구현된 IManipulationEvents 이벤트 싱크에서 COM 개체의 인스턴스를 만들고 참조해야 합니다.

WM_TOUCH 메시지 처리

입력 데이터는 WM_TOUCH 메시지에서 추출한 다음 나중에 올바른 조작 프로세서를 공급하기 위해 처리되어야 합니다.

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;
}

참고 항목

ScreenToClient 함수를 사용하려면 애플리케이션에서 높은 DPI 지원이 있어야 합니다. 높은 DPI를 지원하는 방법에 대한 자세한 내용은 높은 DPI를 참조하세요.

TOUCHINPUT 구조를 적절한 프로세서에 전달

GetTouchInputInfo 함수를 사용하여 WM_TOUCH 메시지에서 데이터를 추출한 후 TOUCHINPUT 구조의 dwFlag 집합에 따라 ProcessUpWithTime, ProcessDownWithTime 또는 ProcessMoveWithTime 함수를 호출하여 데이터를 조작 프로세서에 공급합니다.

참고 항목

여러 조작을 지원할 때 TOUCHINPUT 구조에 정의된 dwID를 사용하여 데이터를 올바른 IManipulationProcessor 개체로 보내야 하는 경우 새 조작 프로세서를 만들어야 합니다.

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);

        ...
    }
}

ManipulationCompleted 내에서 관성 설정

ManipulationCompleted 메서드가 호출된 후 IManipulationProcessor 개체는 관성을 호출하기 위해 IManipulationProcessor에 연결된 IInertiaProcessor 개체의 값을 설정해야 합니다. 다음 코드 예제에서는 IManipulationProcessor 메서드 ManipulationCompleted에서 IInertiaProcessor 개체를 설정하는 방법을 보여 줍니다.

    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);

COM 개체 정리

애플리케이션이 닫히면 COM 개체를 정리해야 합니다. 다음 코드에서는 샘플에 할당된 리소스를 해제하는 방법을 보여 줍니다.

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;
    }
}

다중 터치 조작 애플리케이션, 조작 및 관성 샘플, Windows 터치 샘플