Поделиться через


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

Обновлен: Ноябрь 2007

Подсистема 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) & KeyStates.Down) > 0)
{
    btnNone.Background = Brushes.Red;
}

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

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

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

Классы 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);

Второй раздел прописывается в коде и определяет обработчик событий. Если нажата клавиша со стрелкой влево, и 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;
        }
    }
}

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

В следующем примере цвет 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.
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 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 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, поскольку множественное нажатие клавиш может создавать один знак ввода текста, а одиночное нажатие клавиши может создавать многознаковые строки. Это особенно характерно для таких языков, как китайский, японский и корейский, которые используют IME (Input Method Editor — редакторы методов ввода) для формирования тысячи возможных символов в соответствующем алфавите.

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

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

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, а второй раз — для события нажатия кнопки. Это можно упростить, используя команды вместо непосредственной обработки событий ввода. Команды описаны в этом обзоре и в разделе Общие сведения о системе команд.

Фокус

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

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

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

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

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

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

private void OnLoaded(object sender, RoutedEventArgs e)
{
    // Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton);
}

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

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

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

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

Элемент может быть включен в область фокуса в Язык XAML (Extensible Application Markup Language) посредством задания 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);

Классы в приложении 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 является реализацией 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
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;

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

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

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

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

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

Что дальше?

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

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

См. также

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

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

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

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

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

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

Свойства