Опубликован: 05.08.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 12:

События и команды в WPF

Аннотация: В этой лабораторной работе подробно рассказано о событиях и командах WPF на примере многочисленных практических упражнений. В частности описывается обработка событий клавиатуры и мыши, создание пользовательского события, библиотечные классы команд и многое другое.
Ключевые слова: элементы управления, дерево, приложение, отношение вложенности, ПО, интерфейс, синтаксис, имя события, оболочка, префикс, event, target, объект, значение, маршрутизация, класс, инициализация, анализ, стартовый проект, команда, компилятор, определение, список, информация, клавиатурный модификатор, system, Windows, input, DROP, вывод, маршрут, меню, цвет фона, байт, регистрация, конструктор, файл, поле, расширяющий класс, шаблон, область видимости, пользовательское событие, tapping, пространство имен, функция, string, debug, console application, консоль, полезностью решения, парадигма программирования, уровень модели, связывание, аргумент, исполнение, диспетчер, операции, буфер, header, корневой элемент, контейнер, полное имя, прямой, доступ на выполнение, ошибки времени выполнения, диалоговое окно, место, опция, private, поля класса, модификатор видимости, пользователь, представление, embedded, мышь, leave, paint, vi, узел решения, запись, сигнатура, ссылка, адрес, очередь, журнал изменений, транслятор, программа, текстовый редактор, канал связи

Модель перенаправленных событий

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

В WPF используются элементы управления двух видов:

  1. С одиночным содержимым - производные от ContentControl. Такой элемент может содержать в себе только один ближайший вложенный дочерний элемент.
  2. С множественным содержимым (списковые) - производные от ItemsControl. Такие элементы являются элементами контейнерного типа и могут содержать любое количество ближайших вложенных дочерних элементов.

В WPF структуру приложения можно рассматривать как дерево элементов. Если приложение формируется в XAML, то дерево создается на основе отношений вложенности элементов в разметке. При создании приложения в коде C# дерево создается с учетом того, как строятся коллекции для контейнеров, производных от ItemsControl, и задается содержимое элементов-потомков ContentControl.

В WPF существуют три взгляда на дерево элементов:

  • Логическое дерево
  • Визуальное дерево (дерево отображения)
  • Гибридное дерево

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

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

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

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

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

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

Маршрутизированные события поддерживают следующие стратегии маршрутизации ( RoutingStrategy ):

  • прямую Direct - обрабатывается на источнике, возбудившем событие
  • туннельную Tunnel (тоже самое - тоннельную!) - нисходящая маршрутизация событий
  • пузырьковую Bubble - восходящая маршрутизация событий

Прямые события ведут себя как обычные события в .NET Framework. У нас есть три инградиента при рассмотрении этого рода событий: элемент, событие и прикрепленный обработчик (или несколько обработчиков для делегата события). При регистрации обработчика все три инградиента жестко связываются. Событие элемента возбуждается только на нем и может быть обработано только собственными обработчиками.

Туннельные события могут быть возбуждены любым элементом. Но они всегда начинаются на корневом элементе ( Window, Page ) и передвигаются вниз по дереву элементов, пока не будут обработаны и прерваны ( остановлены ) каким-нибудь из них или не достигнут исходного элемента-источника для события. Это позволяет находящимся выше элементам перехватить событие и обработать его прежде, чем оно достигнет возбудившего его элемента. К именам всех туннельных событий добавлена приставка Preview (например, PreviewMouseDown ), поэтому их иногда еще называют событиями предварительного просмотра.

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

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

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

Существует разновидность пузырьковых событий, которые называются вложенными. Большинство контейнерных элементов (например, Grid ) не имеет события Click, потому что оно для них неконкретизировано. Однако к ним все равно можно прикрепить (присоединить) обработчик наподобие свойства зависимости, позаимствовав событие у дочернего элемента, который способен это событие возбудить. Это не значит, что элемент с несвойственным ему вложенным событием будет способен его возбуждать, но возбужденное другими и движущееся через него событие он будет способен перехватить и обработать.

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

<Grid ButtonBase.Click="Grid_Click">
        <Button Click="Button_Click">
            Генератор Click
        </Button>
    </Grid>

В такой конструкции при щелчке на кнопке Button возбудится событие Click. Это событие начнет всплывать к корню визуального дерева. Прежде всего оно будет обработано самой кнопкой, потому что мы прикрепили обработчик Button_Click. Затем оно будет обработано обработчиком Grid_Click, поскольку мы его тоже предусмотрели. И так далее, пока не достигнет корня визуального дерева. Даже если у кнопки Button не предусмотреть обработчик, то событие все равно будет ею возбуждено при щелчке на ней и начнет всплывать по дереву элементов.

Под маршрутизацией событий подразумевается движение событий (event bubbling) по дереву элементов в поиске своих обработчиков. Маршрутитизацию событий еще называют перенаправлением событий. Механизм этого движения можно рассмотреть на примере событий PreviewMouseDown (туннельное) и MouseDown (пузырьковое). При щелчке курсором мыши на каком-нибудь элементе окна WPF (назовем его целевым - target) вначале возбуждается туннельное событие PreviewMouseDown, которое движется от корня дерева к целевому элементу. Когда оно достигнет корня дерева, на смену ему возбуждается парное событие MouseDown, которое начинает движение в противоположном направлении: от целевого элемента к корню дерева.

В любом элементе по маршруту движения этих событий мы можем прикрепить соответствующий обработчик, перехватить событие и обработать его (а затем, если нужно, остановить). Прикрепленному обработчику вместе с событием передается объект аргументов (обычно вторым параметром). Для событий PreviewMouseDown и MouseDown это будет объект e типа MouseButtonEventArgs, для события Click это будет объект e типа RoutedEventArgs, для события KeyDown - объект e типа KeyEventArgs, и так далее. Каждый из этих объектов имеет булево свойство Handled, с помощью которого в любом из обработчиков можно прервать (остановить) дальнейшую маршрутизацию события, присвоив значение e.Handled=true.

Если по пути к целевому элементу мы останавливаем туннельное событие, то не будет возбуждаться и парное к нему пузырьковое событие. Дальнейшая маршрутизация событий может автоматически прерываться и другими событиями. Так например, при щелчке на кнопке вначале возбуждается туннельное событие PreviewMouseDown, которое движется от корня к кнопке. По достижении кнопки парное событие MouseDown не возбуждается, а вместо него начинает всплывать сгенерированное кнопкой событие Click.

Более того, если кнопка внутри себя содержит дочерние элементы и щелчок выполнен по одному из них, то событие PreviewMouseDown доходит до этого целевого элемента. Затем возбуждается парное пузырьковое событие MouseDown, но поднявшись до элемента кнопки оно подавляется и заменяется всплывающим событием Click. Кнопка сама устанавливает для события MouseDown флаг Handled=true и возбуждает собственное событие щелчка. Это действие соответствует наиболее естественному поведению кнопки.

Обзор библиотечных событий

Если не считать исключений, которые возбуждаются средой исполнения, то библиотечные события WPF делятся на 4 группы:

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

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

События времени жизни элементов
Событие Описание
Initialized Данное событие является обычным, а не перенаправленным, и возникает после создания элемента и определения всех его полей и свойств. Такое состояние характеризуется булевым свойством IsInitialized=true. На этом этапе еще не применены стили и привязка данных. Соседние элементы окна могут быть еще не полностью созданными
Loaded Возникает после события Initialized, когда все окно закончило инициализацию и дополнительно были применены стили и привязка данных к элементу. Это последний этап, за которым следует визуализация элемента. В этот момент он принимает состояние IsLoaded=true
Unloaded Возбуждается сразу после удаления элемента из контейнера или его закрытия

Класс FrameworkElement наследует и реализует интерфейс ISupportInitialize, объявляющий методы BeginInit() и EndInit(). Эти методы автоматически вызываются анализатором разметки, но если создавать элементы в процедурном коде, то их следует вызывать вручную. После вызова метода EndInit() инициализация элемента считается завершенной и возбуждается сигнализирующее событие Initialized.

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

Есть еще события времени жизни окна:

События жизни окна
Событие Описание
Activated Возбуждается при получении окном фокуса ввода системы. По функциональности эквивалентно событию GotFocus элемента управления
Closed Возбуждается после закрытия окна, когда еще объекты дерева являются доступными и их событие Unloaded еще не возникло
Closing Возникает при получении команды на закрытие окна, которую еще ее можно отменить присвоением в обработчике свойству Cancel=true через аргумент CancelEventArgs. Если пользователь не закрывал окно, а просто вышел из системы, то нужно обрабатывать событие уровня приложения System.Windows.Application.SessionEnding
ContentRendered Возникает сразу после первой визуализации окна
Deactivated Возбуждается каждый раз, когда окно теряет фокус ввода при свертывании или переключении на другое окно
LocationChanged Происходит, когда местоположение окна изменяется
SourceInitialized Предусмотрено для поддержки взаимодействия с Win32
StateChanged Происходит при изменении окном свойства WindowState

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

Упражнение 1. Обработка событий клавиатуры

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

События клавиатурного ввода
Событие Стратегия маршрутизации Описание
PreviewKeyDown Tunnel Происходит при нажатии
KeyDown Bubble Происходит при нажатии
PreviewTextInput Tunnel Происходит для символьных клавиш, когда нажатие завершено и элемент получил символ
TextInput Bubble Происходит для символьных клавиш, когда нажатие завершено и элемент получил символ
PreviewKeyUp Tunnel Происходит при отпускании
KeyUp Bubble Происходит при отпускании

Некоторые элементы сами обрабатывают часть этих событий. Они могут блокировать дальнейшее продвижение некоторых из них или возбуждать дополнительные события. Например, элемент TextBox блокирует событие TextInput, а для клавиш-стрелок блокирует и событие KeyDown, но в то же время возбуждает свое событие TextChanged при изменении содержимого поля ввода.

Построим приложение, которое продемонстрирует обработку клавиатурных событий.

  • Создайте решение EventsAndCommands вместе с новым WPF -проектом KeyEvents командой File/New/Project, для этого настройте окно мастера так

Обратите внимание на выбор версии библиотеки .NET Framework 3.0, при более низких версиях шаблоны для WPF станут недоступными. Стартовым новый проект тоже становится автоматически, поскольку он пока первый и единственный в решении. Дерево стартового проекта всегда выделяется в панели Solution Explorer полужирным шрифтом.

  • Заполните файл разметки Window1.xaml следующим дескрипторным кодом
<Window x:Class="KeyEvents.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1: События клавиатуры" 
    Height="300" 
    Width="300"
    MinHeight="300" 
    MinWidth="300"
    Background="#FFD4D0C8"
    >
    <DockPanel LastChildFill="True" Margin="2,2,2,0">
        <DockPanel DockPanel.Dock="Top" LastChildFill="True">
            <Label DockPanel.Dock="Left" Content="Введите текст:" />
            <TextBox Name="textBox"
                PreviewKeyDown="textBox_KeyEvent"
                KeyDown="textBox_KeyEvent"
                PreviewKeyUp="textBox_KeyEvent"
                KeyUp="textBox_KeyEvent"
                PreviewTextInput="textBox_PreviewTextInput"
               />
        </DockPanel>
        <StackPanel DockPanel.Dock="Bottom" Margin="0,5,0,0">
            <CheckBox Name="checkIgnoreRepeat"
                Content="Игнорировать автогенерацию клавиш" 
                IsChecked="True" 
                Click="Check_Click"
                IsTabStop="False"
                />
            <CheckBox Name="checkIgnorePreviewTextInput"
                Content="Игнорировать событие PreviewTextInput" 
                IsChecked="True" 
                Margin="0,2"
                Click="Check_Click"
                IsTabStop="False"
                />
            <CheckBox Name="checkIgnoreSymbol"
                Content="Запретить в TextBox нечисловые клавиши" 
                Margin="15,2"
                Click="Check_Click"
                IsTabStop="False"
                />
            <CheckBox Name="checkIgnoreOther"
                Content="Запретить в TextBox некоторые клавиши" 
                Margin="0,2"
                Click="Check_Click"
                IsTabStop="False"
                />
            <CheckBox Name="checkConvertNumber"
                Content="Конвертировать вывод цифровых клавиш" 
                Margin="0,2"
                Click="Check_Click"
                IsTabStop="False"
                />
            <Button 
                HorizontalAlignment="Right" 
                Content="Очистить" 
                Margin="0,5,0,2"
                Padding="5,0,5,0"
                Click="Button_Click"
                />
        </StackPanel>
        <ListBox Name="listBox" Focusable="False" />
    </DockPanel>
</Window>
  • Пройдитесь по разметке и командой Navigate to Event Handler контекстного меню для записей событий создайте заготовки обработчиков в файле процедурного кода

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

  • Запустите проект и убедитесь, что приведенная разметка реализует следующий интерфейс окна приложения


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

  • Изучите код файла разметки, обеспечивающий требуемую компоновку элементов управления пользовательского интерфейса нашего упражнения

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

  • Удалите из файла Window1.xaml.cs процедурного кода окна все что там есть и заполните его следующим содержимым, которое полностью согласуется с разметкой
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
    
namespace KeyEvents
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            textBox.Focus();
        }
    
        private void textBox_KeyEvent(object sender, KeyEventArgs e)
        {
            if ((bool)checkIgnoreRepeat.IsChecked && e.IsRepeat)
                return;// Игнорировать повторные события
    
            // Запретить действие в TextBox некоторых клавиш
            if(checkIgnoreOther.IsChecked.Value)
                switch (e.Key)
                {
                    case Key.Space: // Пробел
                    case Key.Left:  // Стрелка влево
                    case Key.Right: // Стрелка вправо
                    case Key.Home:  // В начало поля
                    case Key.End:   // В конец поля
                        e.Handled = true;
                        break;
                }
    
            string key = e.Key.ToString();
            // Конвертируем вывод цифровых клавиш основной клавиатуры
            if (checkConvertNumber.IsChecked.Value)
            {
                KeyConverter converter = new KeyConverter();
                key = converter.ConvertToString(e.Key);
            }
    
            string message = e.RoutedEvent.ToString();
            message = message.Substring(message.IndexOf('.') + 1);
            message = String.Format("Event: {0,-25}", message) +
                "\t Key: " + key;
            listBox.Items.Add(message);
            listBox.ScrollIntoView(message);// Видеть последний
        }
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Очищаем поле и список
            textBox.Clear();
            listBox.Items.Clear();
    
            // Возвращаем фокус 
            textBox.Focus();
        }
    
        private void Check_Click(object sender, RoutedEventArgs e)
        {
            // Распознаем и синхронизируем взаимосвязанные CheckBox
            FrameworkElement checkBox = e.Source as FrameworkElement;
            switch (checkBox.Name)
            {
                case "checkIgnoreSymbol":
                    if (checkIgnoreSymbol.IsChecked.Value)
                        checkIgnorePreviewTextInput.IsChecked = false;
                    break;
                case "checkIgnorePreviewTextInput":
                    if (checkIgnorePreviewTextInput.IsChecked.Value)
                        checkIgnoreSymbol.IsChecked = false;
                    break;
            }
    
            // Возвращаем фокус 
            textBox.Focus();
        }
    
        // Для отображаемых символов текстового поля
        private void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            if (checkIgnorePreviewTextInput.IsChecked.Value)
                return;
    
            // Запрещаем в TextBox нечисловые символы 
            short val;
            // Попытка преобразовать в число без генерации исключения
            bool success = Int16.TryParse(e.Text, out val);
            if (!success)
            {
                e.Handled = true;// Останавливаем событие
            }
    
            string message = e.RoutedEvent.ToString();
            message = message.Substring(message.IndexOf('.') + 1);
            message = String.Format("Event: {0,-25}", message) +
                "\t Text: " + e.Text;
            listBox.Items.Add(message);
        }
    }
}

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

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


  • Изучите приведенный код процедурного файла, обратите внимание на некоторые приемы программирования

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

  • В файле разметки переместите (вырежьте из... и вставьте в...) весь блок регистрации событий из элемента TextBox в самый внешний контейнер DockPanel, чтобы схематично окончательный код стал таким
<Window x:Class="KeyEvents.Window1"
    ........................................................
    >
    <DockPanel LastChildFill="True" Margin="2,2,2,0"
        PreviewKeyDown="textBox_KeyEvent"
        KeyDown="textBox_KeyEvent"
        PreviewKeyUp="textBox_KeyEvent"
        KeyUp="textBox_KeyEvent"
        PreviewTextInput="textBox_PreviewTextInput"
        >
        <DockPanel DockPanel.Dock="Top" LastChildFill="True">
            <Label DockPanel.Dock="Left" Content="Введите текст:" />
            <TextBox Name="textBox"
                />
        </DockPanel>
        <StackPanel DockPanel.Dock="Bottom" Margin="0,5,0,0">
            .....................................................
        </StackPanel>
        <ListBox Name="listBox" Focusable="False" />
    </DockPanel>
</Window>
  • Запустите приложение и убедитесь, что функциональность его осталась прежней

Поставляемый событием в его обработчик объект e класса KeyEventArgs содержит не только информацию о нажатой клавише, но и сведения о состоянии модификаторов (расширителей), таких как Shift, Ctrl, Alt. Свойство e.KeyStates информирует о том, в каком состоянии находилась клавиша в момент генерации события: Down, None, Toggled.

Много полезной информации имеет свойство e.KeyboardDevice, представляющее экземпляр класса KeyboardDevice. Из его свойства e.KeyboardDevice.FocusedElement можно узнать, какой элемент в данный момент имеет фокус клавиатурного ввода. Из свойства e.KeyboardDevice.Modifiers объекта-аргумента обработчика можно определить состояние клавиатурных модификаторов (расширителей клавиатуры), например, так

private void KeyEvent(object sender, KeyEventArgs e)
        {
            if ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) 
                == ModifierKeys.Control)
                Console.WriteLine("Нажато расширение Ctrl");
            // Или аналогичный код
            if((e.KeyboardDevice.Modifiers & ModifierKeys.Alt) > 0)
                Console.WriteLine("Нажато расширение Alt");
            // Или аналогичный код
            if ((e.KeyboardDevice.Modifiers & ModifierKeys.Shift) != 0)
                Console.WriteLine("Нажато расширение Shift");
        }

Объект e.KeyboardDevice имеет ряд полезных методов для прослушивания состояния любой интересующей нас клавиши

  • public bool IsKeyDown(System.Windows.Input.Key key)
  • public bool IsKeyUp(System.Windows.Input.Key key)
  • public bool IsKeyToggled(System.Windows.Input.Key key)

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

private void KeyEvent(object sender, KeyEventArgs e)
        {
            if (e.KeyboardDevice.IsKeyDown(e.Key))
                Console.WriteLine("Клавиша {0} нажата", e.Key);
            if (e.KeyboardDevice.IsKeyUp(e.Key))
                Console.WriteLine("Клавиша {0} отпущена", e.Key);
            // Попутно проверяем
            if(e.KeyboardDevice.IsKeyToggled(Key.NumLock))
                Console.WriteLine("Клавиша NumLock включена");
         }

Не только в момент возникновения клавиатурного ввода мы можем получать информацию о состоянии клавиатуры в обработчике, но и в любой интересующий нас момент времени в любом месте программы. Для этого достаточно воспользоваться статическим классом System.Windows.Input. Keyboard, который отслеживает клавиатуру компьютера. По своим возможностям он значительно мощнее класса KeyboardDevice, который мы получаем в обработчике вместе с клавиатурным событием. Вот пример

// Где-то в программе    
            bool Win, Ctrl, Alt, NumLock, CapsLock, ScrollLock;
    
            Win = (Keyboard.Modifiers & ModifierKeys.Windows) > 0;
            Ctrl = (Keyboard.Modifiers & ModifierKeys.Control) > 0;
            Alt = (Keyboard.Modifiers & ModifierKeys.Alt) > 0;
            NumLock = Keyboard.IsKeyToggled(Key.NumLock);
            CapsLock = Keyboard.IsKeyToggled(Key.Capital);
            ScrollLock = Keyboard.IsKeyToggled(Key.Scroll);
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Иван Циферблат
Иван Циферблат
Россия, Таганрог, 36, 2000