本文章是由機器翻譯。

行動運算

加強行動使用者的 Windows Touch 應用程式

Gus Class

下載程式碼範例

Windows 7 介紹 Windows 觸控,增強能力的硬體上具備觸控輸入,並提供實心的平台建置觸控式應用程式。 潛在,這表示您可以開發所有年紀和運算能力的使用者能夠了解用最少數量的訓練或指令的非凡直覺化介面。

這項功能背後魔法是 Windows 觸控 API。 使用此 API,您可以擷取資訊的相關使用者接觸螢幕以及有關使用者 ’s 螢幕上筆勢。 您也可以存取真實世界物理的使用者介面項目。 移動一個螢幕上物件就會變成簡單,只要在真實世界中移動物件。 自動縮放的物件,就像自動縮放 elastic 一件工作台上。 當使用者的互動是 well-implemented 觸控式應用程式時,他們覺得好像互動在未來技術或更好他們 don’t 聲明他們根本使用應用程式。 他們 don’t 必須使用滑鼠、 手寫筆或捷徑按鍵或精確地選取功能表項目,以取得應用程式 ’s 核心功能。

行動使用專門針對應用程式應該納入來確保經驗是很適合使用者 ’s 環境的特定需求。 一個不當實作的觸控式應用程式可以完全擊敗使用 Windows 修改的目的。 Windows 修改使用者體驗指導方針 ( go.microsoft.com/fwlink/?LinkId=156610 ) 反白方式開發人員可以改善在使用者的經驗。 這些方針涵蓋各種案例相關行動應用程式開發人員,並將它變成容易避免潛在的陷阱,修改 Windows 開發。

如果您立即採取從這份文件只有一件事,請記住建立應用程式的目標行動使用者時, 必須考慮特定類型的應用程式的層面。 比方說,如果應用程式所使用的 Windows 控制項就一定要它們的適當大小,而且有足夠的間距,讓使用者可以輕易地接觸。 如果您正在建立應用程式可以利用筆觸,務必正確地處理筆觸動作。

首先要注意的事項

本文章中我採取範例觸控式應用程式,並加強的行動應用程式。 我假設您具有一些知識的 COM 和 Windows 觸控,並擁有 Windows 能夠觸控的硬體。 在 Windows 觸控入門,移至 go.microsoft.com/fwlink/?LinkId=156612 或讀取 msdn.microsoft.com/magazine/ee336016.aspx Yochay Kiriaty ’s 發行項。

此範例是在 code.msdn.microsoft.com/ windowstouchmanip 在 「 MSDN 程式碼庫。 下載項目] 索引標籤包含的兩個.zip 檔案、 行動的增強功能沒有第一個和第二個與它們。 下載名為多重 Manipulators.zip 檔案、 其展開並編譯專案。

若要老實說,使用範例就有些時候像嘗試同時穿著 mittens 執行緒是指針:功能將降低至 frustrates 使用者的點。 比方說如果嘗試在重疊的區域中選取重疊物件將會選取,即可移動這兩個物件。 您也可以調整物件的大小,使它 ’s 小 can’t 調整一次。 我告訴您如何修正這些問題並進行改善使用者經驗,在一般的使用性、 物件選取範圍和自然的使用者介面使用的區域中的其他變更。 請記住您對每一個行動應用程式的考量取決於使用者將會與它互動的方式。 我在這裡討論的問題應該為指導方針只能用於這個特定的應用程式。

一般的使用性

當使用者操作在行動應用程式中的圖形物件時,他必須要能夠執行工作,而不使用鍵盤和滑鼠。 而且,行動使用者正在使用高 DPI 設定,或已連線到多重螢幕時, 的應用程式必須一致地行為。 (高 DPI 需求中會詳細討論在 go.microsoft.com/fwlink/?LinkId=153387 )。

該範例應用程式修改 Windows 隱含位址取得從沒有滑鼠和鍵盤使用者輸入的問題。  使用者可以使用觸控輸入來執行動作,例如物件轉譯縮放比例等等。 相關的考量支援滑鼠和鍵盤輸入,讓使用者可以磁碟機,為了觸控輸入應用程式中操作處理器使用包括滑鼠輸入的任何輸入。 圖 1 顯示如何您可以讓使用者藉由將某些公用程式函式加入範例應用程式 ’s Drawable 類別模擬滑鼠輸入,透過具備觸控輸入。 您也必須將處理常式加入至 WndProc 攔截滑鼠輸入到輸入的處理器 (請參閱 的 圖 2)。

圖 1 模擬的公用程式函式修改 [滑鼠的輸入

VOID Drawable::FillInputData(TOUCHINPUT* inData, DWORD cursor, DWORD eType, DWORD time, int x, int y)
{
    inData->dwID = cursor;
    inData->dwFlags = eType;
    inData->dwTime = time;
    inData->x = x;
    inData->y = y;
}

void Drawable::ProcessMouseData(HWND hWnd, UINT msg, WPARAM wParam, LPARAM
    lParam){
    TOUCHINPUT tInput;
    if (this->getCursorID() == MOUSE_CURSOR_ID){          
        switch (msg){
            case WM_LBUTTONDOWN:
                FillInputData(&tInput, MOUSE_CURSOR_ID, TOUCHEVENTF_DOWN, (DWORD)GetMessageTime(),LOWORD(lParam) * 100,HIWORD(lParam) * 100);
                ProcessInputs(hWnd, 1, &tInput, 0);
                break;

            case WM_MOUSEMOVE:
                if(LOWORD(wParam) == MK_LBUTTON)
                {
                    FillInputData(&tInput, MOUSE_CURSOR_ID, TOUCHEVENTF_MOVE, (DWORD)GetMessageTime(),LOWORD(lParam) * 100, HIWORD(lParam) * 100);
                    ProcessInputs(hWnd, 1, &tInput, 0);
                }          
                break;

            case WM_LBUTTONUP:
                FillInputData(&tInput, MOUSE_CURSOR_ID, TOUCHEVENTF_UP, (DWORD)GetMessageTime(),LOWORD(lParam) * 100, HIWORD(lParam) * 100);            
                ProcessInputs(hWnd, 1, &tInput, 0);
                setCursorID(-1);
                break;
            default:
                break;
        }   
    }     
}

圖 2 WndProc 的變更

case WM_LBUTTONDOWN:
    case WM_MOUSEMOVE:   
    case WM_LBUTTONUP:
        for (i=0; i<drawables; i++){
          // contact start
          if (message == WM_LBUTTONDOWN && draw[i]->IsContacted(LOWORD(lParam), HIWORD(lParam), MOUSE_CURSOR_ID)){
              draw[i]->setCursorID(MOUSE_CURSOR_ID);
          }
          // contact end
          if (message == WM_LBUTTONUP && draw[i]->getCursorID() == MOUSE_CURSOR_ID){
            draw[i]->setCursorID(-1);      
          }
          draw[i]->ProcessMouseData(hWnd, message, wParam, lParam);
        }        
        InvalidateRect(hWnd, NULL, false);
        break;

若要處理高 DPI 需求,您可以新增專案資訊清單組建設定值,來讓應用程式知道 DPI 設定。 您可以這樣,如此當您使用各種 DPI 層級座標空間就可正確。 (如果您想要查看應用程式在變更 DPI 層級之後的行為桌面] 上按一下滑鼠右鍵、 按一下 [個人化然後再變更您的 DPI 層級,在 [顯示] 控制台中)。

下列 XML 會顯示此資訊清單若要讓您的應用程式相容於高 DPI 設定可以定義方式:

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" 
   xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
  <asmv3:application>
    <asmv3:windowsSettings xmlns=
"https://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>

一旦專案資訊清單加入至專案 ’s 屬性,應用程式正確地傳送觸控輸入的資訊至操作處理器不論使用者 ’s DPI 設定。 您也可以使用 ScreenToClient 方法 (請參閱如需詳細資訊的 go.microsoft.com/fwlink/?LinkID=153391 ),確定座標空間設定給應用程式座標,而非螢幕座標。 圖 3 顯示 ProcessInputs 成員函式 Drawable 類別的變更,顯示將螢幕點轉換成用戶端點。 現在當使用者連線到 Windows Touch–enabled PC 外部監視器,應用程式的座標空間將保持一致並 DPI 感知。

圖 3 轉換到用戶端端點的螢幕端點

POINT ptInput;
void Drawable::ProcessInputs(HWND hWnd, UINT cInputs, 
     PTOUCHINPUT pInputs, LPARAM lParam){
  for (int i=0; i < static_cast<INT>(cInputs); i++){
...
      ScreenToClient(hWnd, &ptInput);
                
      if (ti.dwFlags & TOUCHEVENTF_DOWN){
        if (IsContacted( ptInput.x, ptInput.y, ti.dwID) ){
          pManip->ProcessDownWithTime(ti.dwID, static_cast<FLOAT>
(ptInput.x), static_cast<FLOAT>( ptInput.y), ti.dwTime);                  
          setCursorID(ti.dwID);                  
            
          if (!CloseTouchInputHandle((HTOUCHINPUT)lParam)) {
            // Error handling                
          }
        }
      }
      if (pInputs[i].dwFlags & TOUCHEVENTF_MOVE){
        pManip->ProcessMoveWithTime(ti.dwID, static_cast<FLOAT>
(ptInput.x), static_cast<FLOAT>( ptInput.y), ti.dwTime);                  
      }
      if (pInputs[i].dwFlags & TOUCHEVENTF_UP){
        pManip->ProcessUpWithTime(ti.dwID, static_cast<FLOAT>
(ptInput.x), static_cast<FLOAT>( ptInput.y), ti.dwTime);
        setCursorID(-1);
      }      
      // If you handled the message and don’t want anything else done 
      // with it, you can close it
   
  }
}

物件選取範圍

若要確保該物件的選取範圍的函式如使用者預期,使用者必須能夠以自然且直覺的方式選取重疊物件,且使用者必須要能夠選取並輕易地將轉換上較小的表單係數的畫面或有限的觸控輸入解析度的螢幕上的物件。

當應用程式目前操作,當使用者選取重疊的物件時,應用程式會傳送至點使用者要接觸到視窗下方的所有物件的觸控式資料。 若要修改應用程式停止處理觸控輸入之後遇到第一個 touched 的物件時,您必須選取物件,請關閉觸控輸入控點。 圖 4 顯示您可以更新觸控輸入處理常式來停止處理觸控式訊息之後連絡第一個物件, 的方式。

圖 4 更新觸控輸入處理常式

POINT ptInput;
void Drawable::ProcessInputs(HWND hWnd, UINT cInputs, 
     PTOUCHINPUT pInputs, LPARAM lParam){
  BOOL fContinue = TRUE;
  for (int i=0; i < static_cast<INT>(cInputs) && fContinue; i++){
...                
      if (ti.dwFlags & TOUCHEVENTF_DOWN){
        if (IsContacted( ptInput.x, ptInput.y, ti.dwID) ){
          pManip->ProcessDownWithTime(ti.dwID, static_cast<FLOAT>
(ptInput.x), static_cast<FLOAT>(ptInput.y), ti.dwTime);                  
          setCursorID(ti.dwID);                  
            
          fContinue = FALSE;
        }
      }
...
  }
  CloseTouchInputHandle((HTOUCHINPUT)lParam);

}

連絡一個 touched 的物件時,實作這項變更之後就會停止觸控式資料取得傳送到陣列中的其他物件。 若要變更應用程式,讓只有第一個物件在 [滑鼠] 之下輸入接收觸控輸入,您可以輸入處理滑鼠往下 short-circuits 邏輯的滑鼠輸入的輸入的陳述式中參數中的中斷。 圖 5 示範對滑鼠輸入處理常式中 switch 陳述式所做的變更。

圖 5 變更滑鼠輸入處理常式中的 Switch 陳述式

case WM_LBUTTONDOWN:
        for (i=0; i<drawables; i++){
          if (draw[i]->IsContacted(LOWORD(lParam), HIWORD(lParam), MOUSE_CURSOR_ID)){
              draw[i]->setCursorID(MOUSE_CURSOR_ID);
              draw[i]->ProcessMouseData(hWnd, message, wParam, lParam);   
              break;
          }
        }
...

接下來,您應該變更您的應用程式以確保當使用者調整大小的物件,物件將不會使用者無法選取或重新調整其大小過小。 若要解決這個,您可以使用設定操作 API 中若要限制物件可以是如何小大小。 操作處理器公用程式的 Drawable 物件進行下列變更:

void Drawable::SetUpManipulator(void){
  pManip->put_MinimumScaleRotateRadius(4000.0f);  
}

現在當您縮放物件,比例值小於 4,000 centipixels 會忽略由應用程式。 每個 Drawable 物件可以具有唯一條件約束 SetUpManipulator 方法中的 [設定],以確保該物件可在適當的方式中操作。

自然的使用者介面

在設計成自然的外觀及風格的應用程式使用者應該可以在多個物件上執行同時操作。 物件應該具有簡單物理跨越在螢幕類似在真實世界中如何發生作用時,使用者應該將無法操作螢幕以外的物件。

由設計,使用操作 API 的應用程式應該支援同時操作的物件。 因為本例中使用操作 API 同時操作會自動啟用。 當您使用 [筆勢 API 的 Windows 觸控 」 支援,同時操作的物件可能並不等的複合筆勢取景位置調整 + 縮放和縮放 + 旋轉 aren’t 任一個。 基於這個原因中,您應該使用操作 API 時您正在設計 Windows 修改應用程式為目標行動電腦。

Windows 觸控 API 包括 IInertiaProcessor 介面,以啟用簡單物理 (慣性) 的支援。 IInertiaProcessor 使用相同的方法部份為 IManipulationProcessor 介面以簡化慣性已經使用操作的應用程式的新增支援。 若要以便慣性的支援您需要延伸現有事件接收器的操作處理器、 Drawable 物件上加入 IInertiaProcessor 介面執行個體的參考、 從事件接收連線事件資料到 IInertiaProcessor 物件及使用計時器來觸發 IInertiaProcessor 介面,以慣性的觸發程序管理事件。 let’s 查看每個作業中更詳細的說明。

首先您必須更新事件接收器,以啟用傳送資料到 IInertiaProcessor 介面的支援。 事件接收器實作標頭會加入下列成員和建構函式定義:

class CManipulationEventSink : _IManipulationEvents
{
public:
    CManipulationEventSink(IInertiaProcessor *inert, Drawable* d);
    CManipulationEventSink(IManipulationProcessor *manip, IInertiaProcessor *inert, Drawable* d);

...
protected:
    IInertiaProcessor*      m_pInert;
    BOOL fExtrapolating;

您也會新增成員] 和 [存取方法至事件接收器來設定用於計時器的 HWND,如下所示:
公用:

void SetWindow(HWND hWnd) {m_hWnd = hWnd;}
...
private:
...
HWND m_hWnd;

接下來,變更建構函式會採用 IManipulationProcessor 介面以接受 IInertiaProcessor 介面,並新增接受只 IInertiaProcessor 介面的建構函式。 建構函式會採用 IManipulationProcessor 介面會使用來觸發 ManipulationCompleted 事件的慣性 IInertiaProcessor 介面的參考。 使用建構函式只有一個 IInertiaProcessor 介面控點事件的慣性。 圖 6 顯示這些建構函式的實作。

圖 6 的 IManipulationProcessor 和 IInertiaProcesor 建構函式的實作

CManipulationEventSink::CManipulationEventSink(IManipulationProcessor *manip, IInertiaProcessor *inert, Drawable* d){
    drawable = d;
    // Yes, we are extrapolating inertia in this case
    fExtrapolating = false;

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

    m_pManip = NULL;
    m_pInert = inert;    

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

    HRESULT hr = S_OK;

    //Get the container with the connection points
    IConnectionPointContainer* spConnectionContainer;
    
    hr = manip->QueryInterface(
      IID_IConnectionPointContainer, 
      (LPVOID*) &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::CManipulationEventSink(IInertiaProcessor *inert, Drawable* d)
{
    drawable = d;
    // Yes, we are extrapolating inertia in this case
    fExtrapolating = true;

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

    m_pManip = NULL;
    m_pInert = inert;    

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

    HRESULT hr = S_OK;

    //Get the container with the connection points
    IConnectionPointContainer* spConnectionContainer;
    
    hr = inert->QueryInterface(
      IID_IConnectionPointContainer, 
      (LPVOID*) &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);

接下來,您更新 Drawable 類別中,以啟用慣性的支援。 的 圖 7 所示的正向定義應該加入,以及成員變數 pInert。

圖 7 更新 Drawable 類別

interface IInertiaProcessor;
public:
...
    // Inertia Processor Initiation
    virtual void SetUpInertia(void);

...
protected:

    HWND m_hWnd;
    
    IManipulationProcessor* pManip;
    IInertiaProcessor*      pInert;
    CManipulationEventSink* pEventSink;

下列程式碼將示範 SetUpInertia 方法最簡單的實作。 這個方法會完成任何處理、 慣性處理器會重設,然後設定任何組態設定:

void Drawable::SetUpInertia(void){
    // Complete any previous processing
    pInert->Complete();

    pInert->put_InitialOriginX(originX*100);
    pInert->put_InitialOriginY(originY*100);
       
    // Configure the inertia processor
    pInert->put_DesiredDeceleration(.1f);  
}

更新 Drawable 類別之後變更 Drawable 建構函式,以便納入建構函新事件接收器式, 的 圖 8 所示。

圖 8 整合新事件接收器的建構函式

Drawable::Drawable(HWND hWnd){
. . 
  
    // Initialize manipulators  
    HRESULT hr = CoCreateInstance(CLSID_ManipulationProcessor,
          NULL,
          CLSCTX_INPROC_SERVER,
          IID_IUnknown,
          (VOID**)(&pManip)
    );

    // Initialize inertia processor
    hr = CoCreateInstance(CLSID_InertiaProcessor,
          NULL,
          CLSCTX_INPROC_SERVER,
          IID_IUnknown,
          (VOID**)(&pInert)
    );

    //TODO: test HR 
    pEventSink = new CManipulationEventSink(pManip,pInert, this);
    pInertSink = new CManipulationEventSink(pInert, this);
    pEventSink->SetWindow(hWnd);
    pInertSink->SetWindow(hWnd);

    SetUpManipulator();
    SetUpInertia();
    m_hWnd = hWnd;
}

並立即將下列的計時器訊息處理常式新增到主要程式:

case WM_TIMER:
        // wParam indicates the timer ID
        for (int i=0; i<drawables; i++){
            if (wParam == draw[i]->GetIndex() ){
                BOOL b;       
                draw[i]->ProcessInertia(&b);        
            }
        }
    break;

一旦您擁有計時器處理常式,並設定您的計時器,您就需要將觸發它從已完成的訊息事件中其中沒有任何慣性。 圖 9 顯示已完成的事件的啟動計時器,當使用者完成操作物件並停止計時器慣性完成後的變更。

圖 9 變更已完成的事件,以

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;


    if (m_hWnd){
        if (fExtrapolating){
            //Inertia Complete, stop the timer used for processing
            KillTimer(m_hWnd,drawable->GetIndex());
        }else{ 
            // Setup velocities for inertia processor
            float vX, vY, vA = 0.0f;
            m_pManip->GetVelocityX(&vX);
            m_pManip->GetVelocityY(&vY);
            m_pManip->GetAngularVelocity(&vA);

            drawable->SetUpInertia();

            // Set up the touch coordinate data
            m_pInert->put_InitialVelocityX(vX / 100);
            m_pInert->put_InitialVelocityY(vY / 100);        
                          
            // Start a timer
            SetTimer(m_hWnd, drawable->GetIndex(), 50, 0);   
    
            // Reset sets the initial timestamp
            pInert->Reset();     
        }
    }
}

請注意減少計時器間隔,SetTimer 中變得更平穩動畫但觸發程序的結果的第三個參數更更新可能造成效能降低,取決於何種作業事件處理常式的事件執行。 比方說非常平滑動畫變更此值為 5 結果,但因為的額外的呼叫來 CManipulationEventSink::ManipulationDelta 更經常更新視窗。

現在您可以建置並執行應用程式但不使用額外的變更操控的物件將 drift 螢幕外。 若要防止物件關閉螢幕操作,設定 IInertiaProcessor 介面,以使用彈性的界限。 圖 10 顯示應該 SetUpInertia 方法初始化螢幕界限 Drawable 的物件進行的變更。

圖 10 初始化螢幕邊界

void Drawable::SetUpInertia(void){
(...)
            
    // Reset sets the  initial timestamp       
    pInert->put_DesiredDeceleration(.1f);

    RECT rect;
    GetClientRect(m_hWnd, &rect);        

    int width = rect.right - rect.left;
    int height = rect.bottom - rect.top;

    int wMargin = width  * .1;
    int hMargin = height * .1;

    pInert->put_BoundaryLeft(rect.left * 100);
    pInert->put_BoundaryTop(rect.top * 100);
    pInert->put_BoundaryRight(rect.right * 100);
    pInert->put_BoundaryBottom(rect.bottom * 100);

    pInert->put_ElasticMarginTop((rect.top - hMargin) * 100);
    pInert->put_ElasticMarginLeft((rect.left + wMargin) * 100);
    pInert->put_ElasticMarginRight((rect.right - wMargin) * 100);
    pInert->put_ElasticMarginBottom((rect.bottom + hMargin) * 100);

...
}

展望未來

使用 Windows 觸控 API 是將值新增至現有的應用程式的有效方式,則是讓您突顯出來的應用程式的好方法。需要處理內容中將會使用您的應用程式的額外時間,可讓您充分利用 Windows 觸控 API。如果您考慮機動性與您的應用程式的可用性需求,應用程式就會更具直覺性,使用者需要較少的時間來探索它的功能。(如 Windows 觸控,包括完整的文件參考的其他資源可以在 MSDN 上在找 msdn.microsoft.com/library/dd562197 (VS.85).aspx )。

使用發行的 Windows 簡報 Framework (WPF) 和.NET 4,Microsoft 將支援受管理的開發使用控制項,可讓多個連絡人的點。如果您是開發人員使用 Managed 程式碼尋找以增強您的應用程式具有多重輸入支援,此版本是值得簽出的。目前,對於 C# 的受管理 Windows 觸控包裝函式的範例都包含在 Windows SDK 中。

Gus「gclassy」Class* 是一個程式設計寫入器/evangelist 他已進行 Windows 修改、 Tablet PC 和憑證 Microsoft ’s DRMsystems。他討論開發人員因素,並在 gclassy.com 他部落格上提供程式設計範例。*

多虧給來檢閱這份文件的技術專家下列:Xiao Tu

將針對 Gus 提出問題或意見傳送給 goplaces@microsoft.com