|
| ||||||||||||||||||||||||||||||||||||
Паскаль для новичков (часть 18)Спрашивали? Отвечаю…
Запряжем клавуАвтор: Владислав Демьянишин
Сегодня поговорим о клавиатуре, а вернее о том как получать информацию, введенную с клавиатуры. Конечно, в языке Turbo Pascal есть стандартные функции KeyPressed:boolean и ReadKey:char, но чтобы узнать была ли нажата клавиша, необходимо постоянно вызывать первую и если она возвращает true, то следует вызвать и вторую для получения кода нажатой клавиши. При этом следует проверить полученный символ на равенство #0 и если равенство не выполняется, то это значит, что была нажата символьная клавиша.
Ну а если все же равенство выполняется, то необходимо вызвать еще раз стандартную функцию ReadKey чтобы получить символ, соответствующий нажатой в данный момент специальной клавише, коей может быть любая функциональная клавиша, клавиши управления курсором. При этом следует знать, что клавиши Enter, Tab и BackSpace отнесены к символьным клавишам, т.е. после первого вызова ReadKey будет возвращен символ соответствующий этим клавишам, т.е. #13, #9, #8 – соответственно.
Используя данный способ получения информации с клавиатуры необходимо учесть тот факт, что символьные и специальные клавиши могут иметь одинаковые значения типа Char, что может внести путаницу при идентификации нажатой клавиши. Да и сама реализация проверки нажатия и получения кода нажатой клавиши получится несколько громоздкой.
Зачем нам такие сложности, тем более что придется использовать стандартный модуль CRT, о нестабильной работе которого я уже упоминал в предыдущих статьях по практике.
А что если описать свою функцию, которая бы в результате вызова своим единственным возвращаемым результатом сразу давала бы знать нажата ли какая-нибудь клавиша и какая именно была нажата. При этом чтобы каждой клавише соответствовал бы уникальный код, который можно было бы применять в конструкциях условных операторов. Используя этот метод, можно будет легко сепарировать коды специальных и функциональных клавиш от символьных клавиш, т.е. сортировать их по функциональному признаку, что очень удобно, и без запутанных чтений расширенного кода клавиатуры, как это требуется при использовании стандартной функции ReadKey.
Если вы со мной согласны, то давайте начнем формировать код нашего нового модуля KEYBOARD.PAS. Для начала оформим заголовок модуля и опишем константы для наиболее необходимых кодов клавиш. Следуя традиции Windows называть коды клавиш как виртуальные клавиши (VK – Virtual Key), я предлагаю все константы, соответствующие клавишам называть, используя префикс “VK_”. Таким образом, если понадобится переносить Pascal-программу на Delphi, то с этой стороны проблем не возникнет, так как там приняты константы с аналогичными именами, за исключением некоторых.
В виду того, что константы, выстроенные в столбик, займут много строк, а место под статью ограничено, то я разместил десятичные константы в таблице, что экономит место. А вы уж сами перепишите их в модуль столбиком, или скачайте весь модуль целиком. Да, не за горами тот день, когда страницы журналов будут скроллируемые, и не будут рваться как бумага, так как будут из эластичной полимерной пленки с изменяемой контрастностью. А еще лучше, если журнал будущего будет иметь гнездо USB, чтобы можно было сразу применить его содержимое, а не бить руками клавиатуру, набирая ссылки, цитаты, сводные таблицы, примеры исходного кода программ и т.д. Что-то меня занесло не туда. Так о чем это я? Ах да…
Unit KeyBoard;
interface
function InKey : word;
function WaitKey : word;
Теперь можно приступить к написанию функций.
Функция InKeys предназначена для внутреннего использования, поэтому нет нужды ее заголовок помещать в блок interface. Ее код основан на вызове стандартной функции MS-DOS для получения кода нажатой клавиши без ожидания и без вывода ее на экран, к тому же она совсем не реагирует на “Ctrl-Break”. Для вызова этой функции MS-DOS необходимо в регистр AH записать номер функции 6, затем в регистр DL надо поместить значение $0FF. Функция активируется вызовом программного прерывания INT $21, и если при возврате из функции флаг нуля установлен в 1, то буфер клавиатуры пуст, т.е. с момента последнего вызова этой функции новых нажатий клавиш не поступало. Если флаг нуля сброшен в 0, то код нажатой клавиши, а вернее сказать символ, соответствующий нажатой символьной клавише содержится в регистре AL. При этом, если значение регистра AL равно нулю, то значит была нажата специальная клавиша и ее код, его еще называют расширенным, следует получить посредством еще одного вызова INT $21.
implementation
function InKeys : word; assembler;
asm
mov ah,6; mov dl,0ffh; int 21h; jnz @k1
mov ax,VK_KEYOFF; jmp @nd
@k1:cmp al,0; jz @k2
mov ah,0; jmp @nd
@k2:int 21h; mov ah,1
@nd:
end;
Следует отметить, что функция InKeys проверяет буфер клавиатуры, и если он пуст, то она возвращает значение специальной пустой клавиши VK_KEYOFF=511, что значит, что нажатия клавиши не было. Если к моменту вызова функции InKeys произошло нажатие клавиши, то полученный код анализируется, и если этот код соответствует нажатию символьной клавиши, то функция возвращает код соответствующего символа, т.е. результат колеблется в пределах 0..255, который затем легко преобразовать в тип Char. В том случае, когда нажата специальная клавиша, то функция запрашивает ее расширенный код и прибавляет к нему число 256, в итоге значения кодов для специальных клавиш будут варьироваться в пределах 256..510.
Следующая функция InKey предназначена для внешнего использования и возвращает код нажатой клавиши в соответствии с тем, что я говорил в предыдущем абзаце. Правда, данная функция, для большего удобства, преобразует коды клавиш Escape, Enter, BackSpace и Tab из символьных в специальные.
function InKey : word;
var key : word;
begin
key := inkeys;
case key of
27:InKey := VK_ESC;
13:InKey := VK_ENTER;
8:InKey := VK_BACKSPACE;
9:InKey := VK_TAB;
else InKey := key;
end;
end;
Имея такую функцию легко сконструировать некоторый процесс, который должен завершаться при нажатии на клавишу ESC:
uses Keyboard;
begin
repeat
… { процесс }
until InKey = VK_ESC;
end.
Ну и чтобы снова и снова не изобретать велосипед, опишем функцию WaitKey, которая дождется нажатия клавиши, и вернет ее код:
function WaitKey : word;
var key : word;
begin
key := InKey;
while key = VK_KEYOFF do key := InKey;
WaitKey := key;
end;
Например, может понадобиться выдать сообщение “Прервать работу программы и выйти в систему?(Y/N)”, и дождаться ответа от пользователя:
uses Keyboard;
var key : word;
begin
…
writeln(‘Выйти в систему?(Y/N)’);
Key := WaitKey;
if (Key = word(‘Y’)) or (Key = word(‘y’)) then halt;
…
writeln(‘Продолжаем работу.’);
end.
Собственно, функции InKey и WaitKey предоставляют исчерпывающий сервис для осуществления управления программой с клавиатуры. Читатели, которые знакомы хотя бы с классическим Бейсиком, наверняка заметили в названии функции InKey что-то до боли знакомое. Совершенно верно, я позаимствовал этот идентификатор из языка программирования Бейсик, ведь я из того поколения программистов, которые начинали с Бейсика, хотя и не VB.
Но при разработке программ может понадобиться осуществить интенсивный опрос клавиатуры, и чтобы это сделать, может пригодиться функция:
procedure SetKeySpeed( Delay, Rate : byte ); assembler;
asm
mov ax,0305h; mov bh,Delay; and bh,3;
mov bl,Rate; and bl,1Fh; int 16h
end;
которая позволяет управлять задержкой Delay перед первым повторением нажатия клавиши (допустимые значения указаны в таблице 3) , и автоповтор Rate нажатия клавиши при удержании клавиши в нажатом состоянии (допустимые значения указаны в таблице 4). Из таблиц 3 и 4 следует, что если установить оба параметра функции SetKeySpeed в 0, получим максимально быстрый ввод с клавиатуры, который может пригодиться в текстовом редакторе или в игре.
Рискну предположить, что кому-то может понадобиться манипулировать светодиодами на панели клавиатуры, с целью создания примитивной цветомузыки ;-) или при написании собственного драйвера клавиатуры. Для управления светодиодами клавиатуры существует двухбайтная команда $0ED, которую следует послать контроллеру клавиатуры (микросхема микропроцессора Intel 8042), затем дождаться готовности контроллера и послать байт-маску светодиодов, где бит 0, равный 1 включает индикатор ScrollLock, установленный бит 1 включает индикатор NumLock, и бит 2 соответственно – CapsLock. Остальные биты игнорируются. Если сбросить один из трех бит в 0, то соответствующий светодиод погаснет.
Итак, приступим к написанию процедуры SetLed:
procedure SetLed( mask : byte ); assembler;
asm
mov al,0EDh; out 60h,al; call WaitChip8042;
mov al,Mask; out 60h,al
end;
Для посылки управляющей команды следует поместить ее код в регистр AL, и послать его в порт $60, после этого дождаться момента, когда буфер очереди команд контроллера клавиатуры окажется пуст, что будет означать, что команда принята к исполнению и контроллер ожидает байт данных для последней команды. Готовность контроллера можно определить с помощью процедуры WaitChip8042, которая будет рассмотрена далее. Когда контроллер будет готов к приему байта данных, следует в регистр AL поместить байт-маску для светодиодов, структура которого рассматривалась выше и послать его в порт $60.
Теперь рассмотрим процедуру WaitChip8042:
procedure WaitChip8042; assembler;
asm
push cx; mov cx,0FFFFh
@kb:in al,64h; test al,00000010b; loopnz @kb
pop cx
end;
которая позволяет определить, готов ли контроллер клавиатуры к приему информации. Для этого достаточно организовать цикл, например, в $0FFFF итераций, в теле которого будет происходить чтение статуса контроллера (команда in al,64h) в регистр AL, затем производится проверка состояния бита готовности 1 байта статуса (команда test al,00000010b) и если бит сброшен в 0, то цикл прекращается, т.е. мы дождались своего часа ;O)
Только при отладке программы не вздумайте выполнять процедуру SetLed в пошаговом режиме. При посылке контроллеру клавиатуры управляющей команды, за которой должен следовать байт данных, произойдет блокирование ввода с клавиатуры до того момента, пока не будет получен ожидаемый байт данных.
Следовательно, очередное нажатие на клавишу F7 или F8 ни к чему не приведут, так как компьютер зависнет (или зависнет задача MS-DOS, если под Windows). Это происходит вследствие того, что контроллер клавиатуры работает в однозадачном режиме, и поэтому если занят приемом команды, то уж конечно не может сканировать состояние клавиш. Вот когда контроллеры клавиатуры станут многозадачными, уж тогда то … ;o)
В качестве примера послужит программка, зажигающая индикаторы клавиатуры:
Uses Keyboard, Profiler;
begin
repeat
SetLed(random(8));
pause(4);
until InKey = VK_ESC;
end.
Мне осталось лишь откланяться, что я и делаю.
Литература та же.
(функциональные клавиши) Таблица 1
(остальные специальные клавиши) Таблица 2
(задержка перед первым повторением нажатия клавиши) Таблица 3
(автоповтор нажатия клавиши) Таблица 4
Продолжение следует…
© Владислав Демьянишин
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ. Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 18): Запряжшм клаву
| |||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||