_Продолжение, начало см. в МК №46, 51-52, 4, 6-7, 10, 12-13, 16-18, 22, 24, 29, 34, 41, 46, 4, 6, 17, 21, 23 (165, 170-171, 175, 177-178, 181, 183-184, 187-189, 193, 195, 200, 205, 212, 217, 227, 229, 240, 244, 246). Те читатели, которые внимательно следят за моими статьями, имели возможность убедиться, насколько легко можно расширять возможности языка Turbo Pascal, если не ограничиваться стандартными средствами и модулями.

Спрашивали? Отвечаю…

Дружба с Микки Маусом

Ну вот, выводить текст и получать информацию посредством ее ввода с клавиатуры мы уже научились. Все это хорошо, НО для создания программы с настоящим серьезным пользовательским интерфейсом необходима одна маленькая, но немаловажная деталь — работа с манипулятором «Мышь» (Mouse), а вернее, получение управляющих команд пользователя посредством интерфейса мыши и их обработка. Так вот, задача, которую мы сегодня перед собой поставим — это составление универсального модуля, который бы состоял из процедур и функций, позволяющих управлять манипулятором «мышь» и получать информацию о действиях пользователя (кликах и перемещении манипулятора). Своеобразный джентльменский набор самых необходимых процедур и функций.

Ну что же, начнем, как обычно, с блока Interface, описав в нем перечислимый тип TMouseButton для идентификации нажатой клавиши и множественный тип TMouseState, который послужит индикатором того, какая клавиша мыши нажата в данный момент. А так как одновременно могут быть нажаты несколько клавиш, рациональность применения множества в данном случае неоспорима.

Unit MsMouse; interface type TMouseButton = (mbLeft, mbRight, mbMiddle); TMouseState = set of TMouseButton; function Ms_Init : boolean; procedure Ms_CurShow; procedure Ms_CurHide; function Ms_X : word; function Ms_Y : word; procedure Ms_SetPosition( x, y : word ); function Ms_State : byte; procedure Ms_SetMinMaxX( Min, Max : word ); procedure Ms_SetMinMaxY( Min, Max : word ); procedure Ms_SetMickey( Horiz, Vertic : word ); function Ms_Down( Button : TMouseButton):boolean; function Ms_Click( Button : TMouseButton):boolean;

Перейдя к блоку Implementation, опишем типизированную константу Ms_ButtonFlag, начальное значение которой свидетельствует о том, что ни одна клавиша мыши не нажата.

implementation const Ms_ButtonFlag : array [TMouseButton] of boolean = ( false, false, false );

Теперь опишем короткую, но очень важную функцию Ms_Init, с выполнения которой нужно начинать работу программы, чтобы проверить наличие драйвера мыши в памяти. Будет возвращен результат true, если драйвер загружен, и false, если драйвер отсутствует или несовместим со стандартным интерфейсом, принятым Microsoft, но такое на моем веку не встречалось. Так что можно смело загружать драйверы mouse.com, mmouse.com, mouse.sys и другие, вручную или через autoexec.bat. Думаю, вас не нужно этому учить.

Функция состоит из двух машинных команд: mov ax,0 — загружает номер 0 функции инициализации драйвера мыши, а int 33h вызывает обработчик интерфейса мыши (драйвер), который, как правило, позиционируется на программном прерывании $33. При этом драйвер не только откликается и возвращает результат в регистре AX (AX = 0 — драйвера нет, или AX = $0FFFF — драйвер есть), но в регистре BX он может вернуть значение $0FFFF — стандартная мышь Microsoft с двумя клавишами, любое другое значение — мышь нестандартная. Но на это нам уже начхать :-).

Кроме того, драйвер осуществляет сброс аппаратного и программного обеспечения мыши. Т.е. все установки чувствительности и диапазона координат устанавливаются по умолчанию, курсор в центре экрана и невидим, видеостраница 0, работа пользовательского обработчика сообщений мыши заблокирована, чувствительность по горизонтали 8:8 микки/pixel, по вертикали — 16:8 микки/pixel, порог удвоения скорости равен 64 микки/сек.

function Ms_Init : boolean; assembler; asm mov ax,0; int 33h end;

Пример:

Uses msmouse; begin if not Ms_Init then begin { выдаем сообщение об ошибке } writeln('Error: Mouse driver not found.'); halt; end; … end.

С этого момента можно обращаться к драйверу мыши. Наверное, первое, что может нас заинтересовать, это «Гюльчатай, покажи личико!» — то бишь курсор, ради которого мы все это затеяли. Для исполнения нашего желания следует вызвать процедуру Ms_CurShow, которую сейчас опишем:

procedure Ms_CurShow; assembler; asm mov ax,1; int 33h end;

Наверно, уже нет необходимости объяснять, что опять устанавливаем номер вызываемой функции и вызываем ее программным прерыванием $33. Скажу лишь, что функция под номером 1 отобразит (включит) курсор мыши, если при этом будет установлен стандартный текстовый или графический режим, коими являются текстовый режим 8025 и графические режимы CGA, EGA, VGA (разрешение последнего — 320200). Иначе, хотя и будет считаться, что курсор включен, он будет отображаться неправильно или не будет виден вовсе, так как, например, при VESA-режимах драйвер мыши не знает, как правильно рисовать курсор. Мы ему поможем, создав еще один модуль-надстройку, но это будет немного позднее.

Для осуществления собственной прорисовки курсора, например, программисту может понадобиться скрыть (выключить) курсор процедурой Ms_CurHide, которая вызывает функцию драйвера с номером 2.

procedure Ms_CurHide; assembler; asm mov ax,2; int 33h end;

При этом следует помнить, что если курсор мыши был выключен подряд определенное количество раз, чтобы его включить, следует столько же раз выполнить процедуру включения. Я так и вижу, как у некоторых читателей рука тянется к какому-нибудь тяжелому предмету, чтобы запустить в меня. Для тех, кто готов метнуть в меня камнем, скажу, что это придумал не я, а пресловутый Micro… Все камни прошу завернуть в яркую подарочную бумагу и послать посылкой в главный офис Microsoft, а лучше прямо на виллу резиденции гражданина Билла, как это делал кот Матроскин, посылая Шарику кочергу :-).

Особо любопытных могут заинтересовать функции Ms_X и Ms_Y, которые возвращают координаты курсора мыши в пикселях соответственно по горизонтали и вертикали. Даже если курсор мыши будет невидим, он все равно способен перемещаться, иначе зачем нам была бы нужна ента мышь белая :-)?

При этом вызывается функция 3 драйвера мыши, которая дает исчерпывающую информацию о положении курсора в регистрах CX — X, DX — Y и состоянии клавиш в регистре BX. Биты 0, 1 и 2 отвечают за левую (Left), правую (Right) и среднюю (Middle) клавиши соответственно, нулевое значение бита означает, что клавиша отпущена, а 1 — клавиша нажата. Но в данном случае, на мой взгляд, удобней читать координаты отдельно, а состояние клавиш отдельно. Хотя это и не рационально в смысле беспокойства для драйвера, но он не будет за это на нас в обиде.

function Ms_X : word; assembler; asm mov ax,3; int 33h; mov ax,cx end; function Ms_Y : word; assembler; asm mov ax,3; int 33h; mov ax,dx end;

Если возникнет непреодолимое желание установить курсор где-то в другом месте экрана, то в этом сможет помочь процедура Ms_SetPosition, которая позиционирует курсор мыши с новыми координатами X и Y, даже если он невидим. В итоге регистр CX будет загружен значением параметра X, а регистр DX — значением параметра Y, и будет вызвана функция 4 драйвера.

procedure Ms_SetPosition(x, y : word); assembler; asm mov ax,4; mov cx,x; mov dx,y; int 33h end;

Тем, кто, читая мой талмуд, успел себе задать (риторический) вопрос «А где же у него кнопка?!…», внятный ответ сможет дать функция Ms_State, которая возвращает результат типа byte; последний можно легко привести к типу TMouseState путем нехитрой комбинации byte(MouseState):=Ms_State, где MouseState — переменная, заранее описанная с типом TMouseState.

function Ms_State : byte; assembler; asm mov ax,3; int 33h; mov ax,bx end;

Как вы могли заметить, в данном случае вызывается все та же функция драйвера 3, рассмотренная выше. Пример

var MouseState : TMouseState; begin … repeat … byte(MouseState) := Ms_State; if mbLeft in MouseState then begin … end; until mbRight in MouseState; end.

иллюстрирует использование функции Ms_State. При нажатии левой клавиши (mbLeft) выполняется некоторое действие, а при нажатии правой (mbRight) цикл прекращается.

Основываясь на функции Ms_State, можно расширить функциональность модуля, описав функцию Ms_Down, которая позволяет определить, нажата ли затребованная клавиша Button в данный момент. Если да, то вернет true, если нет — false.

function Ms_Down(Button : TMouseButton):boolean; var State : TMouseState; begin Ms_Down := false; byte(State) := Ms_State; if Button in State then Ms_Down := true; end;

Тогда уже известный вам пример будет выглядеть гораздо нагляднее и эффективнее с точки зрения экономии памяти сегмента данных:

begin … repeat … if Ms_Down(mbLeft) then begin … end; until Ms_Down(mbRight); end.

Вы со мной согласны? Не слышу? Ладно, молчание — знак согласия :-).

Следующая функция может помочь в отслеживании полного нажатия-отпускания клавиши (клик — Click). Достаточно лишь указать нужную клавишу, и функция вернет true, если клавиша была нажата и теперь отпущена, или false — если нет. Возможно, у кого-то возникнет вопрос: «А почему нельзя просто отследить отпускание клавиши? Ведь раз произошло отпускание, значит, было и нажатие». Дело в том, что если просто проверять состояние клавиши, то очень часто может фиксироваться факт, что клавиша отпущена, и тогда получится, что будет многократно опознано отпускание клавиши, даже когда это отпускание уже было обработано ранее. Именно для того чтобы устранить такое многократное срабатывание, и нужна функция Ms_Click. Правда, ее следует постоянно вызывать в теле некоторого цикла, чтобы не проморгать момент нажатия и отпускания клавиши. И тогда она зарегистрирует нажатие нужной клавиши в переменной Ms_ButtonFlag, а затем, при отпускании, сообщит нам.

function Ms_Click(Button : TMouseButton):boolean; var State : TMouseState; begin Ms_Click := false; byte(State) := Ms_State; {если ранее нажатие клавиши было зарегистрировано} if Ms_ButtonFlag[Button] then begin {то проверяем, отпущена ли она теперь} if not (Button in State) then begin {раз отпущена, то констатируем событие Click} Ms_Click := true; Ms_ButtonFlag[Button] := false; end; end else if Button in State then {это лишь нажатие, и его следует зарегистрировать} Ms_ButtonFlag[Button] := true; end;

Все тот же пример теперь будет выглядеть немного иначе:

begin … repeat … if Ms_Click(mbLeft) then begin … end; until Ms_Click(mbRight); end.

Обычно чувствительность мыши по умолчанию вполне приемлема, но все же может возникнуть необходимость регулировать чувствительность мыши к перемещению. Для этого пригодится процедура Ms_SetMickey. Надо лишь задать величину, на сколько микки нужно передвинуть корпус мышки, чтобы переместиться на 8 точек по экрану по горизонтали и вертикали.

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

В данном случае параметр Horiz загружается в регистр CX, а Vertic — в регистр DX, и вызывается функция $0f драйвера.

procedure Ms_SetMickey( Horiz, Vertic : word ); assembler; asm mov ax,0fh; mov cx,Horiz; mov dx,Vertic; int 33h end;

(Продолжение следует)

Литература:

1. Диалоговая справочная система Norton Guide.