Мобильные программы

Улучшение приложений Windows Touch для пользователей мобильных устройств

Гас Класс

Загрузка примера кода

В Windows 7 введена подсистема Windows Touch, которая улучшает поддержку сенсорного ввода на соответствующем оборудовании и является отличной платформой для создания приложений с сенсорным вводом. Потенциально это означает, что вы можете разрабатывать интерфейсы, интуитивно понятные пользователям всех возрастов и с любым уровнем познаний в информатике; при этом им потребуется минимальное обучение.

Эта функциональность реализуется с помощью Windows Touch API. Этот API позволяет получать информацию о том, какой точки экрана коснулся пользователь и какие жесты были им выполнены. Кроме того, вы получаете доступ к реальной физике для UI-элементов. Перемещение экранного объекта осуществляется так же легко, как и объекта реального мира. Растягивание объекта аналогично растягиванию резинки. Работая с тщательно продуманным сенсорным приложением, пользователям кажется, что они взаимодействуют с технологией будущего или, что еще лучше, вообще не замечают, что они имеют дело с каким-либо приложением. Чтобы получать доступ к базовой функциональности приложения, им не нужны мышь, стило или комбинации клавиш, а также не требуется выбирать команды меню.

Приложения, адаптированные под мобильные устройства, должны отвечать специфическим требованиям и хорошо встраиваться в среду, в которой работает пользователь. Плохо реализованное приложение с поддержкой сенсорного ввода (далее для краткости — сенсорное приложение) может полностью нарушить весь смысл применения Windows Touch. В руководстве «Windows Touch User Experience» (go.microsoft.com/fwlink/?LinkId=156610) изложены рекомендованные способы улучшения пользовательских сред на мобильных устройствах. Это руководство охватывает различные варианты, относящиеся к созданию мобильных приложений, и помогает избегать потенциально возможных проблем в разработках с применением Windows Touch.

Если вы возьмете из моей статье лишь что-то одно, помните, что при создании приложения, рассчитанного на мобильных пользователей, нужно продумать различные аспекты, специфичные для вашего типа приложений. Например, если оно использует элементы управления Windows, убедитесь в адекватности их размеров и в наличии достаточного интервала между ними, чтобы пользователи могли легко касаться их. Если вы разрабатываете приложение с поддержкой жестов (flicks), позаботьтесь о корректной обработке операций жестов.

Начнем с начала

В этой статье я возьму пример сенсорного приложения и адаптирую его под мобильное применение. Я исхожу из того, что у вас есть какие-то познания в области COM и Windows Touch, а также имеется оборудование, поддерживающее Windows Touch. Вводную информацию о Windows Touch можно получить по ссылке go.microsoft.com/fwlink/?LinkId=156612 или из статьи Йочай Кириати (Yochay Kiriaty) «Мультисенсорные технологии в Windows 7» по ссылке msdn.microsoft.com/magazine/ee336016.aspx..

Упомянутый пример взят из MSDN Code Gallery code.msdn.microsoft.com/ windowstouchmanip. В разделе Downloads содержатся два файла .zip: в первом нет расширений для мобильных устройств, а во втором — есть. Скачайте файл Multiple Manipulators.zip, распакуйте его и скомпилируйте проект.

Если честно, то использование примера иногда подобно попытке пролезть через игольное ушко: функциональность скукоживается до такого предела, что начинает раздражать пользователей. Скажем, если вы хотите выбрать один из перекрывающихся объектов, но делаете это в области их перекрытия, то будете перемещать оба объекта. Кроме того, вы можете изменить размер объекта и сделать его таким маленьким, что вам больше не удастся менять его размер. Я покажу, как устранять такие проблемы и вносить другие изменения для улучшения выбора объектов и удобства использования программы в целом. Помните, что решения, принимаемые вами при разработке каждого мобильного приложения, зависят от того, как пользователи будут взаимодействовать с ним. Предлагаемые мной решения в этой статье можно считать руководством только для данного конкретного приложения.

Удобство использования в целом

Манипулируя графическими объектами в мобильном приложении, пользователь должен иметь возможность выполнять задачи, не прибегая к клавиатуре или мыши. Кроме того, приложение должно корректно работать, если на мобильном устройстве используются высокие настройки DPI или если оно подключено к нескольким экранам. (Требования, связанные с установкой высоких настроек DPI, подробно обсуждаются здесь: go.microsoft.com/fwlink/?LinkId=153387.)

В данном приложении-примере Windows Touch неявно решает проблему получения ввода от пользователя без мыши и клавиатуры.  С помощью сенсорного ввода можно выполнять такие операции, как перемещение объекта, масштабирование и т. д. При создании сенсорного приложения стоит подумать о необходимости поддержки клавиатуры и мыши, чтобы пользователь мог работать с обработчиком манипуляций, применяя любое удобное ему устройство ввода. На рис. 1 показано, как имитировать сенсорный ввод с помощью действий мыши, добавив несколько вспомогательных функций в класс 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, щелкните правой кнопкой мыши рабочий стол, выберите Personalize, а затем смените текущий уровень DPI в разделе Display панели управления.)

Следующий 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>

После добавления этого манифеста в свойствах проекта приложение корректно посылает информацию о сенсорном вводе обработчику манипуляций независимо от пользовательских настроек DPI. Вы также можете задействовать метод ScreenToClient (подробности см. по ссылке go.microsoft.com/fwlink/?LinkID=153391), чтобы координатным пространством считались координаты приложения, а не экранные. На рис. 3 представлены изменения в функции-члене ProcessInputs класса Drawable, которая преобразует экранные точки в клиентские. Теперь, когда пользователь подключает внешний монитор к компьютеру с поддержкой Windows Touch, координатное пространство вашего приложения будет оставаться согласованным и подстраиваться под текущий уровень 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
   
  }
}

Выбор объекта

Чтобы выбор объекта происходил ожидаемым для пользователя образом, нужно предоставить возможность выбора перекрывающихся объектов естественным, интуитивно понятным способом, а также возможность выбора и простой трансформации объектов на экранах малых размеров или на экранах с ограниченным разрешением сенсорного ввода.

В том режиме, в каком приложение работает сейчас, при выборе перекрытого объекта оно посылает сенсорные данные всем объектам, которые находятся под точкой касания. Чтобы приложение прекращало обработку сенсорного ввода после обнаружения первого объекта в точке касания, нужно закрывать при выборе объекта описатель сенсорного ввода. Рис. 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);

}

После реализации этого изменения выбор первого объекта в точке касания приведет к прекращению передачи сенсорных данных другим объектам в массиве. Для изменения приложения, так чтобы только первый объект под курсором мыши получал сенсорный ввод, вы можете выходить из блока switch в выражении обработки ввода для нажатой кнопки мыши; это приведет к замыканию логики обработки ввода от мыши. Изменения в блоке switch обработчика ввода от мыши показаны на рис. 5.

Рис. 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;
          }
        }
...

Далее вы должны изменить свое приложение так, чтобы при изменении размеров объектов пользователем эти объекты не становились столь малыми, что их уже больше не удастся выбирать или масштабировать. Для решения этой проблемы вы можете использовать Manipulations API для задания порогового размера объекта, по достижении которого дальнейшее его уменьшение запрещается. Внесите следующие изменения во вспомогательную функцию обработчика манипуляций (manipulation processor) объекта Drawable:

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

Теперь, когда вы масштабируете объект, значения менее 4000 сентипикселей (1 сентипиксель = 1/100 пикселя) игнорируются приложением. Каждый объект Drawable может иметь уникальные ограничения, задаваемые методом SetUpManipulator.

Естественный UI

В приложении, которое создается в расчете на то, чтобы оно выглядело и работало как можно естественнее, у пользователя должна быть возможность выполнять операции над несколькими объектами одновременно. Физика таких объектов при перемещении по экрану должна быть простой — аналогичной тому, как они вели бы себя в реальном мире; кроме того вы не должны позволять пользователю так манипулировать объектами, чтобы они вообще исчезали с экрана.

По соглашению, приложения, использующие Manipulations API, обязаны поддерживать манипуляции сразу над несколькими объектами. Поскольку в этом примере задействован Manipulations API, такие манипуляции разрешаются автоматически. Если вы применяете Gestures API для поддержки Windows Touch, манипуляции сразу над несколькими объектами невозможны, равно как недопустимы и составные жесты вроде «сдвиг + масштабирование» (pan + zoom) и «масштабирование + поворот» (zoom + rotate). Поэтому, создавая приложение Windows Touch, ориентированное на мобильные компьютеры, вы должны использовать именно Manipulations API.

Windows Touch API включает интерфейс IInertiaProcessor для поддержки простой физики (инерции). IInertiaProcessor применяет некоторые из тех же методов, что и интерфейс IManipulationProcessor, упрощающие добавление поддержки инерции в приложения, которые уже используют манипуляции. Чтобы разрешить поддержку инерции, вы должны расширить существующий приемник событий (event sink) для обработчика манипуляций, добавить ссылку на экземпляр интерфейса IInertiaProcessor объекта Drawable, подключить информацию события от приемника событий к объекту IInertiaProcessor и с помощью таймера заставлять интерфейс IInertiaProcessor инициировать события манипуляций для инерции. Рассмотрим каждую операцию подробнее.

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

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

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

Кроме того, вы добавляете член и метод доступа в приемник событий, чтобы задавать HWND, используемый для таймеров:
public:

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

Далее измените конструктор, который принимает интерфейс IManipulationProcessor (тот в свою очередь принимает интерфейс IInertiaProcessor), и добавьте конструктор, принимающий только интерфейс IInertiaProcessor. Конструктор, принимающий интерфейс IManipulationProcessor, использует ссылку на интерфейс IInertiaProcessor для активации инерции из события ManipulationCompleted. Конструктор, принимающий только интерфейс 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, чтобы включить поддержку инерции. Нужно добавить упреждающее определение (forward definition), показанное на рис. 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. Этот метод завершает любую предыдущую обработку, сбрасывает обработчик инерции (inertia processor) в исходное состояние, а затем настраивает его через конфигурационные параметры:

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;

Подготовив таймер и его обработчик, вы должны внести изменения в событие завершения (completed event), чтобы запускать таймер, когда пользователь завершает манипуляции с объектом, и останавливать этот таймер, как только инерция заканчивается (рис. 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.

Теперь вы можете скомпилировать и запустить приложение, но без дополнительных изменений объекты, над которыми выполняются манипуляции, будут «уплывать» с экрана. Чтобы предотвратить это, настройте интерфейс 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 Touch API — эффективный способ расширения возможностей существующих приложений и отличное средство придания «изюминки» вашим программам. Потратив дополнительное время на изучение контекста, в котором будет использоваться ваше приложение, вы сможете выжать максимум пользы из Windows Touch API. Если вы примете во внимание требования к мобильности и удобству использования, приложение станет более интуитивно понятным, а пользователям не понадобится много времени на освоение его функциональности. (Дополнительные ресурсы, включая полную документацию по Windows Touch, см. в MSDN по ссылке msdn.microsoft.com/library/dd562197(VS.85).aspx).

С появлением Windows Presentation Framework (WPF) и .NET 4 Microsoft будет поддерживать разработку на основе управляемого кода с применением элементов управления, имеющих несколько контактных точек. Если вы работаете с управляемым кодом и стремитесь расширить свое приложение поддержкой сенсорного ввода с использованием разнообразных устройств, вам стоит обратить внимание на этот выпуск .NET. В настоящее время примеры управляемых оболочек Windows Touch для C# включаются в Windows SDK.

Гас «GCLASSY» Класс (Gus «GCLASSY» Class) — программист и евангелист в Microsoft, где работал над Windows Touch, Tablet PC и DRM-системами Microsoft. Обсуждает проблемы разработки и предлагает примеры программирования в своем блоге на gclassy.com..

Выражаю благодарность за рецензирование данной статьи эксперту Ксяо Ту (Xiao Tu).

Вопросы и комментарии (на английском языке) присылайте по адресу goplaces@microsoft.com.