Событийно-управляемый ввод

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

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

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

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

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

Само событие, в частности движение мыши, это явление материальной реальности и, чтобы сделать его доступным для программ, внутренние компоненты ОС строят специализированные описания событий, обычно называемые сообщениями (message) или для выразительности просто событиями (event). Для нас, как программных пользователей операционной системы, существенно, что какой-то ее компонент подготавливает такие специализированные структуры данных для программ, способных их использовать.

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

В Windows программа для текстового окна может запросить сообщение путем вызова системной функции ReadConsoleInput. Эта функция имеет прототип

BOOL ReadConsoleInput(HANDLE hConsInput,

INPUT_RECORD* buffer, DWORD len, DWORD* actlen).

Кроме хэндла для указания консольного буфера ввода (в частности хэндла стандартного файла ввода) эта функция содержит адрес буфера, который представляет собой в общем случае массив записей типа INPUT_RECORD для размещения некоторого числа записей сообщений ввода. Размер массива выбирается программистом. Размер этого массива записей задается при вызове в параметре len. В простейших случаях массив buffer состоит из единственного элемента – для размещения единственного очередного сообщения, и параметр len берется поэтому равным 1. В общем случае, когда при вызове функции задается len, не равное 1, следует в программе после обращения к ReadConsoleInput проверять, сколько записей о вводе было действительно получено (с помощью параметра actlen), и принимать соответствующие действия с учетом этого фактического значения. Заметим, что функция ReadConsoleInput возвращает управление в вызвавшую ее программу только после появления сообщения о вводе. До этого момента вычислительный процесс выполнения программы, содержащей такую функцию, приостановлен (блокирован).

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

typedef struct _INPUT_RECORD {

WORD EventType;

union {

KEY_EVENT_RECORD KeyEvent;

MOUSE_EVENT_RECORD MouseEvent;

WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;

MENU_EVENT_RECORD MenuEvent;

FOCUS_EVENT_RECORD FocusEvent;

} Event;

} INPUT_RECORD, *PINPUT_RECORD;

 

Ключевым полем в этой записи является тип события EventType, его значения задаются предопределенными константами, описанными как

// EventType flags:

#define KEY_EVENT 0x0001 // Event contains key event record

#define MOUSE_EVENT 0x0002 // Event contains mouse event record

#define WINDOW_BUFFER_SIZE_EVENT 0x0004

// Event contains window change event record

#define MENU_EVENT 0x0008 // Event contains menu event record

#define FOCUS_EVENT 0x0010 // event contains focus change

Это ключевое поле EventType своим значением определяет более детальное строение сообщения. Если его значение равно KEY_EVENT, то на самом деле в этой универсальной структуре вся остальная часть (обобщенно называемая Event) есть структура типа KEY_EVENT_RECORD. Если же значение типа равно MOUSE_EVENT, то остальная часть есть в действительности структура типа MOUSE_EVENT_RECORD. (Остальные типы сообщений и их структуры в данном изложении рассматриваться не будут.)

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

. . .

ReadConsoleInput( hInput, &inpbuf, 1, &actlen);

if (inpbuf. EventType = = KEY_EVENT)

{обработка события от клавиатуры,

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

ipnbuf.Event.KeyEvent }

if (inpbuf. EventType = = MOUSE_EVENT)

{обработка события от мыши,

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

ipnbuf.Event.MouseEvent }

. . . ,

в которой предполагается, что использованные информационные объекты данных где-то раньше описаны как

HANDLE hInput;

INPUT_RECORD inpbuf;

DWORD actlen;

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

Конкретные внутренние поля типов данных для сообщений от клавиатуры и мыши будут рассмотрены несколько дальше.