Общие сведения о входных данных

Подсистема Windows Presentation Foundation (WPF) предоставляет мощный интерфейс API для получения входных данных от разнообразных устройств, включая мышь, клавиатуру, сенсорный экран и перо. В этом разделе описываются службы, предоставляемые подсистемой WPF, и объясняется архитектура систем ввода.

В этом разделе содержатся следующие подразделы.

  • Входной API
  • Маршрутизация событий
  • Обработка событий ввода
  • Ввод текста
  • Сенсорный экран: прикосновение и манипуляция
  • Фокус
  • Положение мыши
  • Захват мыши
  • Команды
  • Система ввода и базовые элементы
  • Что дальше?
  • Связанные разделы

Входной API

Предоставление исходного входного API основано на базовых классах элемента UIElement, ContentElement, FrameworkElement и FrameworkContentElement. Дополнительные сведения о базовых элементах см. в разделе Общие сведения о базовых элементах. Эти классы предоставляют функциональные возможности назначения событий ввода, связанных в том числе с нажатием клавиш, кнопок мыши, колеса мыши, движением мыши, управлением фокуса и захватом мыши. Размещая API входных данных в базовых элементах вместо обработки всех событий ввода в качестве службы, архитектура ввода позволяет определенному объекту пользовательского интерфейса порождать события ввода, которые поддерживают схему маршрутизации событий. Это позволяет обрабатывать событие ввода нескольким элементам одновременно. Многие события ввода имеют пары связанных с ними событий. Например, событие нажатие клавиши связано с событиями KeyDown и PreviewKeyDown. Разница в этих событиях заключается в том, как они маршрутизируются к целевому элементу. События предварительного просмотра проходят по нисходящей по дереву элементов от корневого элемента до целевого. Пузырьковые события "всплывают" от целевого элемента до корневого элемента. Маршрутизация событий в WPF более подробно обсуждается в этом обзоре и в разделе Общие сведения о перенаправленных событиях.

Классы клавиатуры и мыши

В дополнение ко входному API на базовых классах элемента, класс Keyboard и классы Mouse предоставляют дополнительные API для работы с вводом с клавиатуры и мыши.

Примерами входных API в классе Keyboard являются свойство Modifiers, которое возвращает нажатую в данный момент ModifierKeys, и метод IsKeyDown, определяющий, нажата ли указанная клавиша.

В следующем примере метод GetKeyStates используется для определения того, находится ли Key в нажатом положении.

            ' Uses the Keyboard.GetKeyStates to determine if a key is down.
            ' A bitwise AND operation is used in the comparison. 
            ' e is an instance of KeyEventArgs.
            If (Keyboard.GetKeyStates(Key.Return) And KeyStates.Down) > 0 Then
                btnNone.Background = Brushes.Red
// Uses the Keyboard.GetKeyStates to determine if a key is down.
// A bitwise AND operation is used in the comparison. 
// e is an instance of KeyEventArgs.
if ((Keyboard.GetKeyStates(Key.Return) & KeyStates.Down) > 0)
{
    btnNone.Background = Brushes.Red;
}

Примерами API входных данных в классе Mouse являются свойство MiddleButton, получающее состояние средней кнопки мыши, и свойство DirectlyOver, получающее элемент, на который в данный момент наведен указатель мыши.

В следующем примере производится определение того, находится ли LeftButton на мыши в состоянии Pressed.

            If Mouse.LeftButton = MouseButtonState.Pressed Then
                UpdateSampleResults("Left Button Pressed")
            End If
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
    UpdateSampleResults("Left Button Pressed");
}

Классы Mouse и Keyboard подробно рассматриваются далее в этом обзоре.

Ввод с пера

WPF имеет интегрированную поддержку Stylus. Stylus — рукописный ввод, ставший популярным благодаря устройствам типа Tablet PC. Приложения WPF могут обрабатывать перо как мышь с помощью API мыши, но WPF также предоставляет перо как абстрактное устройство, которое использует модель, похожую на модель клавиатуры и мыши. Все связанные с пером APIs содержат слово "Stylus".

Поскольку перо может вести себя как мышь, то приложения, поддерживающие только ввод с мыши, могут автоматически получать определенный уровень поддержки ввода с помощью пера. При использовании пера таким образом, приложению дается возможность обработать соответствующее событие пера, а затем — соответствующее событие мыши. Кроме того, службы более высокого уровня — например, ввод рукописных данных — доступны через абстрактное устройство пера. Дополнительные сведения о рукописных данных в качестве входных данных см. в разделе Начало работы с рукописными данными.

Маршрутизация событий

FrameworkElement может содержать в своей модели содержимого другие элементы в качестве дочерних элементов, формируя тем самым дерево элементов. Обрабатывая события, родительский элемент в WPF может участвовать в вводе, ориентированном на дочерние элементы или других потомков. Это особенно полезно для построения элементов управления из меньших элементов управления — процесса, известного под названием "построение элемента управления" или "комбинирование". Дополнительные сведения о деревьях элемента и о том, как деревья элемента связаны с маршрутами события, см. в разделе Деревья в WPF.

Маршрутизация события — это процесс пересылки событий нескольким элементам, позволяющий определенному объекту или элементу, расположенному вдоль маршрута, посредством обработки предоставлять значимый ответ событию, которое может порождаться другим элементом. Перенаправленные события используют один из трех механизмов маршрутизации: прямая маршрутизация, маршрутизация по восходящей и маршрутизация по нисходящей. При прямой маршрутизации уведомляется только исходный элемент, и событие не маршрутизируется с любыми другими элементами. Однако, событие с прямой маршрутизацией тем не менее предоставляет некоторые дополнительные возможности, доступные только для маршрутизированных событий (в отличие от стандартных событий CLR). Маршрутизация по восходящей обрабатывает дерево элемента следующим образом: вначале уведомляется элемент, являющийся источником события, затем его родительский элемент и т.д. Нисходящая маршрутизация начинается в корне дерева элемента и спускается вниз, заканчивая исходным элементом источника. Дополнительные сведения о маршрутизируемых событиях см. в разделе Общие сведения о перенаправленных событиях.

События ввода WPF обычно представлены парами, состоящими из событий нисходящей и восходящей маршрутизации. События нисходящей маршрутизации отличаются от событий восходящей маршрутизации префиксом "Preview". Например, PreviewMouseMove является нисходящей версией события перемещения мыши, а MouseMove —восходящей версией этого события. Пары событий являются соглашением, реализованным на уровне элемента и не являются неотъемлемой характеристикой системы событий WPF. Дополнительные сведения см. в подразделе "События ввода WPF" раздела Общие сведения о перенаправленных событиях.

Обработка событий ввода

Для получения входных данных в элементе обработчик событий должен быть связан с данным конкретным событием. В XAML это осуществляется напрямую: необходимо сослаться на имя события как на атрибут элемента, который будет ожидать это событие. Затем нужно установить значение атрибута равным имени определяемого обработчика событий в зависимости от делегата. Обработчик событий должен быть прописан в коде (например, на C#) и может быть включен в файл кода программной части.

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

Пример события ввода с клавиатуры

В приведенном ниже примере ожидается нажатие клавиши со стрелкой влево. Создается StackPanel, имеющая Button. Обработчик событий для ожидания нажатия клавиши со стрелкой влево присоединяется к экземпляру Button.

Первый раздел в примере создает StackPanel и Button и присоединяет обработчик событий для KeyDown.

<StackPanel>
  <Button Background="AliceBlue"
          KeyDown="OnButtonKeyDown"
          Content="Button1"/>
</StackPanel>
            ' Create the UI elements.
            Dim keyboardStackPanel As New StackPanel()
            Dim keyboardButton1 As New Button()

            ' Set properties on Buttons.
            keyboardButton1.Background = Brushes.AliceBlue
            keyboardButton1.Content = "Button 1"

            ' Attach Buttons to StackPanel.
            keyboardStackPanel.Children.Add(keyboardButton1)

            ' Attach event handler.
            AddHandler keyboardButton1.KeyDown, AddressOf OnButtonKeyDown
// Create the UI elements.
StackPanel keyboardStackPanel = new StackPanel();
Button keyboardButton1 = new Button();

// Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue;
keyboardButton1.Content = "Button 1";

// Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1);

// Attach event handler.
keyboardButton1.KeyDown += new KeyEventHandler(OnButtonKeyDown);

Второй раздел прописывается в коде и определяет обработчик событий. Если нажата клавиша со стрелкой влево, и Button имеет фокус клавиатуры, программа запускает обработчик и изменяет цвет Background кнопки Button. Если нажата клавиша не со стрелкой влево, цвет Background кнопки Button изменяется обратно на первоначальный.

        Private Sub OnButtonKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
            Dim source As Button = TryCast(e.Source, Button)
            If source IsNot Nothing Then
                If e.Key = Key.Left Then
                    source.Background = Brushes.LemonChiffon
                Else
                    source.Background = Brushes.AliceBlue
                End If
            End If
        End Sub
private void OnButtonKeyDown(object sender, KeyEventArgs e)
{
    Button source = e.Source as Button;
    if (source != null)
    {
        if (e.Key == Key.Left)
        {
            source.Background = Brushes.LemonChiffon;
        }
        else
        {
            source.Background = Brushes.AliceBlue;
        }
    }
}

Пример события ввода мыши

В следующем примере цвет Background кнопки Button изменяется, когда указатель мыши появляется над Button. Цвет Background восстанавливается, если указатель мыши покидает Button.

Первый раздел примера создает элемент управления StackPanel и Button и присоединяет обработчики событий для событий MouseEnter и MouseLeave к Button.

<StackPanel>
  <Button Background="AliceBlue"
          MouseEnter="OnMouseExampleMouseEnter"
          MouseLeave="OnMosueExampleMouseLeave">Button

  </Button>
</StackPanel>
            ' Create the UI elements.
            Dim mouseMoveStackPanel As New StackPanel()
            Dim mouseMoveButton As New Button()

            ' Set properties on Button.
            mouseMoveButton.Background = Brushes.AliceBlue
            mouseMoveButton.Content = "Button"

            ' Attach Buttons to StackPanel.
            mouseMoveStackPanel.Children.Add(mouseMoveButton)

            ' Attach event handler.
            AddHandler mouseMoveButton.MouseEnter, AddressOf OnMouseExampleMouseEnter
            AddHandler mouseMoveButton.MouseLeave, AddressOf OnMosueExampleMouseLeave
// Create the UI elements.
StackPanel mouseMoveStackPanel = new StackPanel();
Button mouseMoveButton = new Button();

// Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue;
mouseMoveButton.Content = "Button";

// Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton);

// Attach event handler.
mouseMoveButton.MouseEnter += new MouseEventHandler(OnMouseExampleMouseEnter);
mouseMoveButton.MouseLeave += new MouseEventHandler(OnMosueExampleMouseLeave);

Второй раздел примера прописывается в коде и определяет обработчики событий. Когда мышь появляется над Button, цвет Background кнопки Button изменяется на SlateGray. Когда мышь покидает Button, цвет Background кнопки Button изменяется обратно на AliceBlue.

        Private Sub OnMouseExampleMouseEnter(ByVal sender As Object, ByVal e As MouseEventArgs)
            ' Cast the source of the event to a Button.
            Dim source As Button = TryCast(e.Source, Button)

            ' If source is a Button.
            If source IsNot Nothing Then
                source.Background = Brushes.SlateGray
            End If
        End Sub
private void OnMouseExampleMouseEnter(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.SlateGray;
    }
}
        Private Sub OnMosueExampleMouseLeave(ByVal sender As Object, ByVal e As MouseEventArgs)
            ' Cast the source of the event to a Button.
            Dim source As Button = TryCast(e.Source, Button)

            ' If source is a Button.
            If source IsNot Nothing Then
                source.Background = Brushes.AliceBlue
            End If
        End Sub
private void OnMosueExampleMouseLeave(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.AliceBlue;
    }
}

Ввод текста

Событие TextInput позволяет ожидать ввода текста аппаратно-независимым образом. Клавиатура является основным средством для ввода текста, но речь, рукописная запись и другие устройства ввода также могут создавать ввод текста.

Для ввода с клавиатуры WPF сначала вызывает соответствующие события KeyDown/KeyUp. Если эти события не обрабатываются, и клавиша является текстовой (а не клавишей элемента управления, например, клавишей со стрелкой или функциональной клавишей), то вызывается событие TextInput. Не всегда существует простое однозначное соответствие между KeyDown / KeyUp и событиями TextInput, поскольку множественное нажатие клавиш может создавать один знак ввода текста, а одиночное нажатие клавиши может создавать многознаковые строки. Это особенно характерно для таких языков, как китайский, японский и корейский, которые используют Input Method Editors (IMEs) для формирования тысячи возможных символов в соответствующем алфавите.

Когда WPF отправляет событие KeyUp/KeyDown, Key устанавливается равным Key.System, если нажатие клавиш может стать частью события TextInput (например, если нажата комбинация ALT+S). Это позволяет коду в обработчике событий KeyDown проверять наличие поля Key.System и при его обнаружении прерывать обработку, передавая ее обработчику сгенерированного впоследствии события TextInput. В этих случаях различные свойства аргумента TextCompositionEventArgs могут быть использованы для определения первоначальных нажатий клавиш. Аналогично, если активна IME, то Key имеет значение Key.ImeProcessed, а ImeProcessedKey дает первоначальное нажатие клавиши или клавиш.

В следующем примере определяется обработчик для события Click и обработчик для события KeyDown.

Первый сегмент кода или разметки создает интерфейс пользователя.

<StackPanel KeyDown="OnTextInputKeyDown">
  <Button Click="OnTextInputButtonClick"
          Content="Open" />
  <TextBox> . . . </TextBox>
</StackPanel>
            ' Create the UI elements.
            Dim textInputStackPanel As New StackPanel()
            Dim textInputeButton As New Button()
            Dim textInputTextBox As New TextBox()
            textInputeButton.Content = "Open"

            ' Attach elements to StackPanel.
            textInputStackPanel.Children.Add(textInputeButton)
            textInputStackPanel.Children.Add(textInputTextBox)

            ' Attach event handlers.
            AddHandler textInputStackPanel.KeyDown, AddressOf OnTextInputKeyDown
            AddHandler textInputeButton.Click, AddressOf OnTextInputButtonClick
// Create the UI elements.
StackPanel textInputStackPanel = new StackPanel();
Button textInputeButton = new Button();
TextBox textInputTextBox = new TextBox();
textInputeButton.Content = "Open";

// Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton);
textInputStackPanel.Children.Add(textInputTextBox);

// Attach event handlers.
textInputStackPanel.KeyDown += new KeyEventHandler(OnTextInputKeyDown);
textInputeButton.Click += new RoutedEventHandler(OnTextInputButtonClick);

Второй сегмент кода содержит обработчики событий.

        Private Sub OnTextInputKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
            If e.Key = Key.O AndAlso Keyboard.Modifiers = ModifierKeys.Control Then
                handle()
                e.Handled = True
            End If
        End Sub

        Private Sub OnTextInputButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
            handle()
            e.Handled = True
        End Sub

        Public Sub handle()
            MessageBox.Show("Pretend this opens a file")
        End Sub
private void OnTextInputKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.O && Keyboard.Modifiers == ModifierKeys.Control)
    {
        handle();
        e.Handled = true;
    }
}

private void OnTextInputButtonClick(object sender, RoutedEventArgs e)
{
    handle();
    e.Handled = true;
} 

public void handle()
{
    MessageBox.Show("Pretend this opens a file");
}

Так как события ввода маршрутизируются по восходящей по маршруту событий, StackPanel получает входные данные независимо от того, какой элемент имеет фокус клавиатуры. Сначала уведомляется элемент управления TextBox, и обработчик OnTextInputKeyDown вызывается только в том случае, если TextBox не обработал входные данные. При использовании события PreviewKeyDown вместо события KeyDown, сначала вызывается обработчик OnTextInputKeyDown.

В этом примере логика обработки записывается два раза — один раз для CTRL+O, а второй раз — для события нажатия кнопки. Это можно упростить, используя команды вместо непосредственной обработки событий ввода. Команды описаны в этом обзоре и в разделе Общие сведения о системе команд.

Сенсорный экран: прикосновение и манипуляция

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

WPF предоставляет два типа событий при прикосновении к сенсорному экрану: прикосновение и манипуляция. События прикосновения предоставляют необработанные данные о позиции и движении каждого пальца на сенсорном экране. События манипуляции интерпретируют эти данные, распознавая в них определенные действия. В этом разделе рассматриваются оба типа событий.

Предварительные требования

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

  • Microsoft Visual Studio 2010.

  • Windows 7.

  • Устройство (например, сенсорный экран) с поддержкой технологии Windows Touch.

Терминология

В отношении сенсорного экрана используется следующая терминология.

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

  • Мультисенсорный ввод — это прикосновение в нескольких точках сенсорного экрана одновременно. Windows 7 и WPF поддерживают мультисенсорный ввод. Сведения об управлении с помощью сенсорного экрана, приведенные в документации по WPF, также относятся к мультисенсорному вводу.

  • Манипуляция возникает, когда входные данные с сенсорного экрана интерпретируются как физическое действие над предметом. В WPF события манипуляции интерпретируют входные данные с сенсорного экрана как манипуляции трех категорий: преобразование, расширение и поворот.

  • touch device представляет устройство для сенсорного ввода, например сенсорный экран с однопальцевым вводом.

Элементы управления, реагирующие на входные данные от сенсорного экрана

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

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

События сенсорного экрана

В базовых классах UIElement, UIElement3D и ContentElement определены события, которые можно зарегистрировать в приложении, чтобы оно реагировало на входные данные с сенсорного экрана. Имеет смысл использовать события сенсорного экрана, если приложение должно реагировать на другие типы прикосновений помимо манипуляций с объектами. Например, чтобы приложение позволяло пользователю рисовать на экране одним или двумя пальцами, оно должно реагировать на события сенсорного экрана.

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

Как и события клавиатуры и мыши, события сенсорного экрана являются перенаправленными. События, начинающиеся со слова Preview, — это события с нисходящей маршрутизацией, а начинающиеся со слова Touch — с восходящей. Дополнительные сведения о маршрутизируемых событиях см. в разделе Общие сведения о перенаправленных событиях. При обработке этих событий можно получить позицию точки прикосновения к экрану относительно элемента, вызвав метод GetTouchPoint или GetIntermediateTouchPoints.

Рассмотрим поведение событий сенсорного экрана на примере ситуации, когда пользователь прикасается к элементу одним пальцем, передвигает палец в пределах элемента и убирает палец с экрана. На следующем рисунке показан порядок событий с восходящей маршрутизацией (события с нисходящей маршрутизацией не указаны для наглядности).

События сенсорного экрана

Последовательность событий сенсорного экрана.

В приведенном ниже списке описана последовательность событий на предыдущем рисунке.

  1. Событие TouchEnter возникает один раз, когда пользователь прикасается к элементу на экране.

  2. Событие TouchDown возникает один раз.

  3. Событие TouchMove возникает многократно по мере того, как пользователь передвигает палец по экрану, касаясь элемента.

  4. Событие TouchUp возникает один раз, когда пользователь перестает касаться элемента на экране.

  5. Событие TouchLeave возникает один раз.

Если пользователь касается экрана несколькими пальцами одновременно, события возникают для каждого пальца.

События манипуляции

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

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

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

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

Несколько типов манипуляций можно выполнять одновременно.

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

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

В классе UIElement определены следующие события манипуляции.

По умолчанию объект UIElement не получает эти события манипуляции. Чтобы объект UIElement получал события манипуляции, задайте свойству UIElement.IsManipulationEnabled значение true.

Путь выполнения событий манипуляций

Рассмотрим ситуацию, когда пользователь "бросает" объект. Пользователь касается пальцем объекта, передвигает палец по сенсорному экрану на небольшое расстояние и снимает палец с экрана, не прекращая движение. В результате объект двигается, повинуясь прикосновению пользователя, и продолжает движение после того, как пользователь перестает касаться экрана.

На следующем рисунке показан путь выполнения событий манипуляции и приведены важные сведения о каждом событии.

События манипуляции

Последовательность событий манипуляции.

В приведенном ниже списке описана последовательность событий на предыдущем рисунке.

  1. Событие ManipulationStarting возникает, когда пользователь прикасается к объекту на экране. Помимо прочих возможностей, это событие позволяет задать свойство ManipulationContainer. В последующих событиях позиция манипуляций будет указываться относительно позиции, сохраненной в свойстве ManipulationContainer. Для всех событий, кроме ManipulationStarting, это свойство доступно только для чтения, поэтому его можно задать только во время события ManipulationStarting.

  2. Затем возникает событие ManipulationStarted. Оно указывает место начала манипуляции.

  3. Событие ManipulationDelta возникает многократно по мере того, как пальцы пользователя двигаются по сенсорному экрану. Свойство DeltaManipulation класса ManipulationDeltaEventArgs сообщает о том, как интерпретируется манипуляция: как движение, расширение или преобразование. Здесь совершается большая часть работы по манипулированию объектом.

  4. Событие ManipulationInertiaStarting возникает, когда пользователь перестает касаться объекта. Это событие позволяет задать интенсивность замедления объекта при движении по инерции. Это позволяет имитировать различные физические свойства предметов и окружающей среды (при необходимости). Предположим, к примеру, что в приложении имеется два объекта, представляющие реальные предметы, и один из них тяжелее другого. Можно сделать так, чтобы тяжелый объект замедлялся быстрее, чем легкий.

  5. Событие ManipulationDelta возникает многократно по мере действия инерции. Обратите внимание, что это событие возникает, когда пальцы пользователя двигаются по сенсорному экрану и когда WPF имитирует движение по инерции. Другими словами, событие ManipulationDelta возникает и до, и после события ManipulationInertiaStarting. Свойство ManipulationDeltaEventArgs.IsInertial сообщает, происходит ли событие ManipulationDelta при движении по инерции, так что можно проверять это свойство и совершать различные действия в зависимости от его значения.

  6. Событие ManipulationCompleted возникает при завершении манипуляции или движения по инерции. Таким образом, после того как произошли все события ManipulationDelta, возникает событие ManipulationCompleted, сообщающее о завершении манипуляции.

В классе UIElement также определено событие ManipulationBoundaryFeedback. Оно возникает, когда в событии ManipulationDelta вызывается метод ReportBoundaryFeedback. Событие ManipulationBoundaryFeedback позволяет приложению или компоненту визуально указать о том, что объект достиг границы движения. Например, класс Window обрабатывает событие ManipulationBoundaryFeedback, немного сдвигая окно при достижении его границы.

Манипуляцию можно отменить, вызвав метод Cancel с аргументами события при любой манипуляции, кроме события ManipulationBoundaryFeedback. При вызове метода Cancel события манипуляции более не возникают, и вместо них при касании сенсорного экрана возникают события мыши. Следующая таблица характеризует взаимосвязь между моментом отмены манипуляции и возникновением событий мыши.

Событие, в котором вызывается метод Cancel

События мыши, возникающие для уже совершенных действий пользователя

ManipulationStarting и ManipulationStarted

События нажатия кнопки мыши.

ManipulationDelta

События нажатия и отпускания кнопки мыши.

ManipulationInertiaStarting и ManipulationCompleted

События нажатия кнопки мыши, движения мыши и отпускания кнопки мыши.

Обратите внимание, что если вызвать метод Cancel во время манипуляции по инерции, метод возвращает значение false и события мыши не генерируются.

Связь между событиями сенсорного экрана и событиями манипуляции

Элемент UIElement всегда может принимать события сенсорного экрана. Когда для свойства IsManipulationEnabled задано значение true, элемент UIElement может принимать как события сенсорного экрана, так и события манипуляции. Если событие TouchDown не обрабатывается (то есть, для свойства Handled задано значение false), логика манипуляции перехватывает касание элемента и создает события манипуляции. Если для свойства Handled задано значение true в событии TouchDown, логика манипуляции не создает события манипуляции. На следующем рисунке показана связь между событиями сенсорного экрана и событиями манипуляции.

События сенсорного экрана и события манипуляции

Связь между событиями сенсорного экрана и событиями манипуляции

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

Фокус

Приложение WPF имеет два основных механизма фокуса: фокус клавиатуры и логический фокус.

Фокус клавиатуры

Фокус клавиатуры относится к элементу, который получает ввод данных с клавиатуры. На всем рабочем столе одновременно может существовать только один элемент, имеющий фокус ввода. В приложении WPF элемент, имеющий фокус ввода, будет иметь для свойства IsKeyboardFocused значение true. Статический метод Keyboard FocusedElement возвращает элемент, который в настоящее время имеет фокус клавиатуры.

Фокус клавиатуры может быть получен переходом к элементу или щелчком мыши на таких элементах, как, например, TextBox. Фокус клавиатуры также может быть получен программными средствами с помощью метода Focus для класса Keyboard. Метод Focus пытается предоставить фокус ввода указанному элементу. Элемент, возвращенный Focus, является элементом, который в данный момент времени имеет фокус клавиатуры.

В запросе на выполнение элемента для получения фокуса клавиатуры значениям свойства Focusable и свойств IsVisible должно быть присвоено значение true. Некоторые классы, например Panel, имеют Focusable, по умолчанию заданное равным false. Таким образом, если этот элемент долен иметь возможность получать фокус, разработчику может потребоваться установить для этого свойства значение true.

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

        Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Sets keyboard focus on the first Button in the sample.
            Keyboard.Focus(firstButton)
        End Sub
private void OnLoaded(object sender, RoutedEventArgs e)
{
    // Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton);
}

Дополнительные сведения о фокусе клавиатуры содержатся в разделе Общие сведения о фокусе.

Логический фокус

Логический фокус относится к свойству FocusManager.FocusedElement в области фокуса. В приложении может существовать несколько элементов, имеющих логический фокус, но в отдельной области фокуса только один элемент может иметь логический фокус.

Областью фокуса является элемент-контейнер, который хранит путь FocusedElement в своей области. Когда фокус покидает область фокуса, фокусируемый элемент теряет фокус клавиатуры, но сохраняет логический фокус. При возвращении фокуса к области фокуса, фокусируемый элемент снова получает фокус клавиатуры. Это позволяет передвигать фокус клавиатуры между несколькими областями фокуса, но гарантирует то, что в области фокуса элемент с фокусом остается неизменным до возвращения фокуса.

Элемент может быть включен в область фокуса в Extensible Application Markup Language (XAML) посредством задания FocusManager присоединенного свойства IsFocusScope в значение true, или в коде, посредством задания вложенного свойства с помощью метода SetIsFocusScope.

В следующем примере создается объект StackPanel в области фокуса путем установки присоединенного значения IsFocusScope.

<StackPanel Name="focusScope1" 
            FocusManager.IsFocusScope="True"
            Height="200" Width="200">
  <Button Name="button1" Height="50" Width="50"/>
  <Button Name="button2" Height="50" Width="50"/>
</StackPanel>
            Dim focuseScope2 As New StackPanel()
            FocusManager.SetIsFocusScope(focuseScope2, True)
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);

Классы в приложении WPF, являющиеся областью фокуса, по умолчанию являются объектами: Window, Menu, ToolBar и ContextMenu.

Элемент, имеющий фокус клавиатуры, также будет иметь и логический фокус для области фокуса, которой он принадлежит. Таким образом, при установке фокуса на элемент при помощи метода Focus в классе Keyboard или в классах базового элемента будет предпринята попытка задания ему фокуса клавиатуры и логического фокуса.

Чтобы определить элемент с фокусом в области фокуса, используйте GetFocusedElement. Чтобы изменить элемент с фокусом для области фокуса, используйте SetFocusedElement.

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

Положение мыши

API ввода WPF предоставляет полезные сведения относительно координатного пространства. Например, известно, что координата (0,0) является левой верхней координатой, но для какого элемента в дереве? Для элемента, который является целью ввода? Для элемента, к которому присоединяется обработчик событий? Или для какого-нибудь другого элемента? Чтобы избежать путаницы, API ввода в WPF требует указания системы координат при работе с координатами, полученными посредством мыши. Метод GetPosition возвращает координаты указателя мыши относительно указанного элемента.

Захват мыши

Устройства мыши имеют модальную характеристику, называющуюся захватом мыши. Захват мыши используется для поддержания промежуточного состояния ввода при запуске операции перетаскивания, что делает необязательным выполнение других операций, касающихся номинального положения указателя мыши на экране. Во время перетаскивания пользователь не может щелкнуть без прерывания перетаскивания, что лишает смысла подсказки при наведении мыши в течении того времени, пока захват мыши удерживается исходным перетаскиванием. Система ввода предоставляет как APIs, который может определить состояние захвата мыши, так и APIs, который может изменить захват мыши на определенный элемент или очистить состояние захвата мыши. Дополнительные сведения об операциях перетаскивания см. в разделе Общие сведения о перетаскивании.

Команды

Команды допускают обработку ввода на более подробном семантическом уровне, чем ввод устройства. Команды являются простыми директивы, например, Cut, Copy, Paste или Open. Команды полезны для централизации командной логики. Доступ к одной и той же команде может быть осуществлен из Menu, на ToolBar или посредством сочетания клавиш. Команды также предоставляет механизм для отключения элементов управления в том случае, когда команда становится недоступной.

RoutedCommand является реализацией WPF для ICommand. При выполнении RoutedCommand события PreviewExecuted и Executed вызываются на целевом объекте команды, который осуществляет маршрутизацию по дереву элементов подобно другому вводу. Если целевой объект команды не задан, то в качестве цели команды используется элемент, в котором установлен фокус клавиатуры. Логика, выполняющая команду, присоединяется к CommandBinding. Когда событие Executed достигает CommandBinding для указанной команды, вызывается ExecutedRoutedEventHandler на CommandBinding. Этот обработчик выполняет действие команды.

Для получения дополнительных сведений о командах см. раздел Общие сведения о системе команд.

WPF предоставляет библиотеку общих команд, состоящих из ApplicationCommands, MediaCommands, ComponentCommands, NavigationCommands и EditingCommands. Разработчик также может определить собственные команды.

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

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
            ' Creating the UI objects
            Dim mainStackPanel As New StackPanel()
            Dim pasteTextBox As New TextBox()
            Dim stackPanelMenu As New Menu()
            Dim pasteMenuItem As New MenuItem()

            ' Adding objects to the panel and the menu
            stackPanelMenu.Items.Add(pasteMenuItem)
            mainStackPanel.Children.Add(stackPanelMenu)
            mainStackPanel.Children.Add(pasteTextBox)

            ' Setting the command to the Paste command
            pasteMenuItem.Command = ApplicationCommands.Paste
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;

Дополнительные сведения о командах в WPF см. в разделе Общие сведения о системе команд.

Система ввода и базовые элементы

События ввода, такие как вложенные события, определенные классами Mouse, Keyboard и Stylus, вызываются системой ввода и вводятся в определенном месте модели объекта в зависимости от проверки нажатия визуального дерева во время выполнения.

Каждое событие, которое Mouse, Keyboard и Stylus определяют как вложенное событие, также переопределяется классами базового элемента UIElement и ContentElement как новое перенаправленное событие. Перенаправленные события базового элемента создаются классами, обрабатывающими исходное вложенное событие и многократно использующими данные события.

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

Что дальше?

В этом разделе было описано несколько методов обработки ввода в WPF. Также были рассмотрены различные типы событий ввода и механизмы события маршрутизации, используемые WPF.

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

См. также

Основные понятия

Общие сведения о фокусе

Общие сведения о системе команд

Общие сведения о перенаправленных событиях

Общие сведения о базовых элементах

Другие ресурсы

Свойства