Обработка инерции в неуправляемом коде

В этом разделе объясняется, как использовать интерфейс IInertiaProcessor для обработки инерции в неуправляемом коде.

Общие сведения

Чтобы использовать инерцию в неуправляемом коде, необходимо реализовать приемники событий как для обработчика манипуляций, так и для обработчика инерции. Начните с добавления поддержки манипуляций в приложение, как описано в разделе Добавление поддержки манипуляций в неуправляемый код. Обратите внимание, что для поддержки манипуляции требуется использовать сенсорные сообщения, а не жесты для передачи данных о событиях обработчику манипуляций. После выполнения манипуляции необходимо также реализовать второй приемник событий для событий, создаваемых интерфейсом IInertiaProcessor , или потребуется изменить существующий приемник событий, чтобы вместить как события , созданные интерфейсами IInertiaProcessor , так и IManipulationProcessor . В этом примере проще начать с приемника событий, созданного для раздела Добавление поддержки манипуляций в неуправляемый код, и добавить второй конструктор, который работает с обработчиком инерции, а не с обработчиком манипуляций. Таким образом, реализация приемника событий может функционировать как для обработчика манипуляций, так и для обработчика инерции. Помимо добавления второго конструктора, приемник событий будет иметь переменную, указывающую, будет ли он выполнять операции на основе входных данных инерции, а не на вводе манипуляции.

Добавление поддержки инерции в приемник событий обработчика манипуляций

В следующем коде показан новый конструктор приемника событий, новые переменные-члены для интерфейса IInertiaProcessor и флаг, указывающий, экстраполирует ли приемник инерцию.

    CManipulationEventSink(IManipulationProcessor *pManip, IInertiaProcessor *pInert, HWND hWnd);
    CManipulationEventSink(IInertiaProcessor *pInert, HWND hWnd);
    IInertiaProcessor*      m_pInert;
    BOOL fExtrapolating; 

После того как заголовок класса содержит новые конструкторы и флаг, указывающий, выполняется ли экстраполяция, вы можете реализовать приемник событий, чтобы иметь отдельные блоки обработки для событий IManipulationProcessor и IInertiaProcessor . Конструктор, который принимает IManipulationProcessor и IInertiaProcessor , должен установить для флага fExtrapolating значение false, указывающее, что это обработчик событий IManipulationProcessor . В следующем коде показано, как можно реализовать конструктор для приемника событий, использующего IManipulationProcessor .

CManipulationEventSink::CManipulationEventSink(IManipulationProcessor *pManip, IInertiaProcessor *pInert, HWND hWnd)
{
    m_hWnd = hWnd;

    //Set initial ref count to 1.
    m_cRefCount = 1;

    fExtrapolating=FALSE;

    m_pManip = pManip;
    
    m_pInert = pInert;
    
    m_pManip->put_PivotRadius(-1);

    m_cStartedEventCount = 0;
    m_cDeltaEventCount = 0;
    m_cCompletedEventCount = 0;

    HRESULT hr = S_OK;

    //Get the container with the connection points.
    IConnectionPointContainer* spConnectionContainer;
    
    hr = pManip->QueryInterface(
      IID_IConnectionPointContainer, 
          (LPVOID*) &spConnectionContainer
        );
    //hr = manip->QueryInterface(&spConnectionContainer);
    if (spConnectionContainer == NULL){
        // something went wrong, try to gracefully quit
        
    }

    //Get a connection point.
    hr = spConnectionContainer->FindConnectionPoint(__uuidof(_IManipulationEvents), &m_pConnPoint);
    if (m_pConnPoint == NULL){
        // something went wrong, try to gracefully quit
    }

    DWORD dwCookie;

    //Advise.
    hr = m_pConnPoint->Advise(this, &dwCookie);
}

В следующем коде показано, как можно реализовать конструктор для приемника событий, использующего IInertiaProcessor . Этот конструктор устанавливает для флага fExtrapolating значение true, указывая, что этот экземпляр класса приемника событий будет выполнять экстраполяции и выполнять все операции перемещения, которые ранее выполнялись событиями обработчика манипуляций.

CManipulationEventSink::CManipulationEventSink(IInertiaProcessor *pInert, HWND hWnd)
{
    m_hWnd = hWnd;

    m_pInert = pInert;
    //Set initial ref count to 1.
    m_cRefCount = 1;

    fExtrapolating=TRUE;

    m_cStartedEventCount = 0;
    m_cDeltaEventCount = 0;
    m_cCompletedEventCount = 0;

    HRESULT hr = S_OK;

    //Get the container with the connection points.
    IConnectionPointContainer* spConnectionContainer;
    
    hr = pInert->QueryInterface(
      IID_IConnectionPointContainer, 
          (LPVOID*) &spConnectionContainer
        );
    //hr = manip->QueryInterface(&spConnectionContainer);
    if (spConnectionContainer == NULL){
        // something went wrong, try to gracefully quit        
    }

    //Get a connection point.
    hr = spConnectionContainer->FindConnectionPoint(__uuidof(_IManipulationEvents), &m_pConnPoint);
    if (m_pConnPoint == NULL){
        // something went wrong, try to gracefully quit
    }
    DWORD dwCookie;

    //Advise.
    hr = m_pConnPoint->Advise(this, &dwCookie);
}   

Примечание

Реализация класса приемника событий из приемника событий обработчика манипуляций повторно используется в качестве приемника событий для обработчика инерции.

 

Теперь при построении этого класса CManipulationEventSink его можно создать в качестве приемника событий для обработчика манипуляций или приемника событий для обработчика инерции. При создании в качестве приемника событий обработчика инерции флаг fExtrapolating будет иметь значение true, указывающее, что события манипуляции должны быть экстраполированы.

Примечание

ManipulationStarted будет вызываться интерфейсами IManipulationProcessor и IInertiaProcessor .

 

При запуске манипуляции задаются свойства интерфейса IInertiaProcessor . В следующем коде показано, как обрабатывается событие started.

HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationStarted( 
    /* [in] */ FLOAT x,
    /* [in] */ FLOAT y)
{
    m_cStartedEventCount ++;       

    // set origins in manipulation processor
    m_pInert->put_InitialOriginX(x);
    m_pInert->put_InitialOriginY(y);
    
    RECT screenRect;

    HWND desktop = GetDesktopWindow();
    GetClientRect(desktop, &screenRect);

    // physics settings
    // deceleration is units per square millisecond
    m_pInert->put_DesiredDeceleration(.1f);

    // set the boundaries        
    screenRect.left-= 1024;
    m_pInert->put_BoundaryLeft  ( static_cast<float>(screenRect.left   * 100));
    m_pInert->put_BoundaryTop   ( static_cast<float>(screenRect.top    * 100));
    m_pInert->put_BoundaryRight ( static_cast<float>(screenRect.right  * 100));
    m_pInert->put_BoundaryBottom( static_cast<float>(screenRect.bottom * 100));
    
    
    // Elastic boundaries - I set these to 90% of the screen 
    // so... 5% at left, 95% right, 5% top,  95% bottom
    // Values are whole numbers because units are in centipixels
    m_pInert->put_ElasticMarginLeft  (static_cast<float>(screenRect.left   * 5));
    m_pInert->put_ElasticMarginTop   (static_cast<float>(screenRect.top    * 5));
    m_pInert->put_ElasticMarginRight (static_cast<float>(screenRect.right  * 95));
    m_pInert->put_ElasticMarginBottom(static_cast<float>(screenRect.bottom * 95));
    
    
    return S_OK;
}

В этом примере для перемещения окна используются дельты манипуляции. В следующем коде показано, как обрабатывается событие delta.

HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationDelta( 
    /* [in] */ FLOAT x,
    /* [in] */ FLOAT y,
    /* [in] */ FLOAT translationDeltaX,
    /* [in] */ FLOAT translationDeltaY,
    /* [in] */ FLOAT scaleDelta,
    /* [in] */ FLOAT expansionDelta,
    /* [in] */ FLOAT rotationDelta,
    /* [in] */ FLOAT cumulativeTranslationX,
    /* [in] */ FLOAT cumulativeTranslationY,
    /* [in] */ FLOAT cumulativeScale,
    /* [in] */ FLOAT cumulativeExpansion,
    /* [in] */ FLOAT cumulativeRotation)
{
    m_cDeltaEventCount ++;
        
    RECT rect;
            
    GetWindowRect(m_hWnd, &rect);
        
    int oldWidth =  rect.right-rect.left;
    int oldHeight = rect.bottom-rect.top;            

    // scale and translate the window size / position    
    MoveWindow(m_hWnd,                                              // the window to move
        static_cast<int>(rect.left + (translationDeltaX / 100.0f)), // the x position
        static_cast<int>(rect.top + (translationDeltaY/100.0f)),    // the y position
        static_cast<int>(oldWidth * scaleDelta),                    // width
        static_cast<int>(oldHeight * scaleDelta),                   // height
        TRUE);                                                      // redraw
                     
    return S_OK;
}

В этом примере события завершения манипуляции запускают или останавливают таймер, который вызывает Process в интерфейсе IInertiaProcessor . В следующем коде показано, как обрабатывается завершенное событие манипуляции.

HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationCompleted( 
    /* [in] */ FLOAT x,
    /* [in] */ FLOAT y,
    /* [in] */ FLOAT cumulativeTranslationX,
    /* [in] */ FLOAT cumulativeTranslationY,
    /* [in] */ FLOAT cumulativeScale,
    /* [in] */ FLOAT cumulativeExpansion,
    /* [in] */ FLOAT cumulativeRotation)
{
    m_cCompletedEventCount ++;

    m_fX = x;
    m_fY = y;

    // place your code handler here to do any operations based on the manipulation   
    
    if (fExtrapolating){
        //Inertia Complete, stop the timer used for processing      
        KillTimer(m_hWnd,0);        
    }else{ 
        // setup velocities for inertia processor
        float vX = 0.0f;
        float vY = 0.0f;
        float vA = 0.0f;
        m_pManip->GetVelocityX(&vX);
        m_pManip->GetVelocityY(&vY);
        m_pManip->GetAngularVelocity(&vA);

        // complete any previous processing
        m_pInert->Complete();
        
        // Reset sets the  initial timestamp
        m_pInert->Reset();
                
        // 
        m_pInert->put_InitialVelocityX(vX);
        m_pInert->put_InitialVelocityY(vY);        
        
        m_pInert->put_InitialOriginX(x);
        m_pInert->put_InitialOriginY(y);
        
           
        // Start a timer
        SetTimer(m_hWnd,0, 50, 0);        
    }

    return S_OK;
}

В следующем коде показано, как можно интерпретировать WM_TIMER сообщения в WndProc для выполнения вызовов Process в интерфейсе IInertiaProcessor .

case WM_TIMER:       
  if (g_pIInertProc){
    BOOL b;       
    g_pIInertProc->Process(&b);        
  }
  break;

Совместная инициализация обработчика инерции и обработчика манипуляций и инициализация приемников событий

После изменения приемника событий для поддержки IManipulationProcessor и IInertiaProcessor вы можете инициализировать приемники событий и настроить их для запуска из приложения. В следующем коде показано, как выделяются указатели интерфейса.

//Include windows.h for touch events
#include "windows.h"  

// Manipulation implementation file
#include <manipulations_i.c>
    
// Smart Pointer to a global reference of a manipulation processor, event sink
IManipulationProcessor* g_pIManipProc;
IInertiaProcessor*      g_pIInertProc;

В следующем примере кода показано, как создать экземпляры интерфейсов.

   HRESULT hr = CoInitialize(0);
        
   hr = CoCreateInstance(CLSID_ManipulationProcessor,
       NULL,
       CLSCTX_INPROC_SERVER,
       IID_IUnknown,
       (VOID**)(&g_pIManipProc)
   );
   
   hr = CoCreateInstance(CLSID_InertiaProcessor,
       NULL,
       CLSCTX_INPROC_SERVER,
       IID_IUnknown,
       (VOID**)(&g_pIInertProc)
   );

В следующем примере кода показано, как создать приемники событий с учетом указателей интерфейса и зарегистрировать окно для сенсорного ввода.

   g_pManipulationEventSink = new CManipulationEventSink(g_pIManipProc, g_pIInertProc, hWnd);
   g_pManipulationEventSink = new CManipulationEventSink(g_pIInertProc, hWnd);


   RegisterTouchWindow(hWnd, 0);  

Инерция