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

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

API ввода

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

Классы Keyboard и Mouse

В дополнение к 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) & KeyStates.Down) > 0)
{
    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) And KeyStates.Down) > 0 Then
    btnNone.Background = Brushes.Red

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

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

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

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

Ввод с помощью пера

В WPF встроена поддержка для Stylus. Stylus — это ввод с помощью пера, который широко применяют пользователи планшетных компьютеров. Приложения WPF могут обрабатывать перо как мышь с помощью API мыши, но WPF также предоставляет перо как абстрактное устройство, использующее модель, аналогичную клавиатуре и мыши. Все связанные с пером API-интерфейсы содержат слово 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.
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);
' 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

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

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

Пример события ввода с помощью мыши

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

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

<StackPanel>
  <Button Background="AliceBlue"
          MouseEnter="OnMouseExampleMouseEnter"
          MouseLeave="OnMosueExampleMouseLeave">Button
          
  </Button>
</StackPanel>
// 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);
' 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

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

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

Ввод текста

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

При ввода с клавиатуры WPF сначала отправляет соответствующие события KeyDown/KeyUp. Если эти события не обрабатываются и клавиша является текстовой (а не управляющей, например клавишей со стрелкой или функциональной клавишей), то затем вызывается событие TextInput. События KeyDown/KeyUp и TextInput не всегда можно просто сопоставить "один к одному", так как при нажатии нескольких клавиш может создаваться один знак текстового ввода, а при нажатии одной клавиши могут создаваться строки из нескольких знаков. В первую очередь это относится к таким языкам, как китайский, японский и корейский, которые используют редакторы метода ввода (IME) для формирования тысяч возможных символов в соответствующем алфавите.

Когда 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.
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);
' 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

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

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

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

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

Касание и манипуляция

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

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

Необходимые компоненты

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

  • 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.

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

The sequence of touch events. События касания

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

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

  2. Событие TouchDown происходит один раз.

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

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

  5. Событие TouchLeave происходит один раз.

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

События обработки

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

  • Поместите палец на объект и переместите палец по сенсорному экрану, чтобы выполнить сдвиг. Обычно такое действие перемещает объект.

  • Поместите два пальца на объект и сведите или разведите их, чтобы выполнить расширение. Обычно такое действие изменяет размеры объекта.

  • Поместите два пальца на объект и поворачивайте их вокруг друг друга, чтобы выполнить действие поворота. Обычно такое действие выполняет циклический сдвиг объекта.

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

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

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

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

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

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

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

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

The sequence of manipulation events. События манипуляции

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

  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. Это событие возникает при вызове метода ReportBoundaryFeedback в событии ManipulationDelta. Событие ManipulationBoundaryFeedback позволяет приложениям или компонентам обеспечивать визуальную обратную связь, когда объект достигает границы. Например, класс Window обрабатывает событие ManipulationBoundaryFeedback, вызывающее небольшое перемещение окна при контакте с его краем.

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

Событие, вызываемое отменой События мыши, возникающие для ввода, который уже произошел
ManipulationStarting и ManipulationStarted. События нажатия кнопки мыши.
ManipulationDelta События нажатия кнопки мыши и перемещения мыши.
ManipulationInertiaStarting и ManipulationCompleted. События нажатия кнопки мыши, перемещения мыши и отпускания кнопки мыши.

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

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

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

Relationship between touch and manipulation events События касания и манипуляции

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

  • Когда первое сенсорное устройство создает событие TouchDown для UIElement, логика манипуляции вызывает метод CaptureTouch, с помощью которого создается событие GotTouchCapture.

  • При возникновении GotTouchCapture логика манипуляции вызывает метод Manipulation.AddManipulator, с помощью которого создается событие ManipulationStarting.

  • Когда происходит событие TouchMove, с помощью логики манипуляции создаются события ManipulationDelta, которые возникают перед событием ManipulationInertiaStarting.

  • Когда последнее сенсорное устройство вызывает событие TouchUpдля элемента, с помощью логики манипуляции создается событие ManipulationInertiaStarting.

Фокус

Есть два основных понятия, относящихся к фокусу в WPF: фокус клавиатуры и логический фокус.

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

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

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

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

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

private void OnLoaded(object sender, RoutedEventArgs e)
{
    // Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton);
}
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

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

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

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

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

Элемент можно поместить в область фокуса в языке 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>
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);
Dim focuseScope2 As New StackPanel()
FocusManager.SetIsFocusScope(focuseScope2, True)

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

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

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

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

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

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

Захват мыши

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

Команды

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

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

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

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

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

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
// 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;
' 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

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

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

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

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

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

Дальнейшие действия

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

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

См. также