Программирование на Паскале. Выпуск 11


Работа с мышью. 3

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

Благодарность ответившим

Я очень благодарен тем, кто написал мне письма с указанием на недочеты. Сейчас отвечу на два:
  1. Была отмечена такая "ошибка", как отсутствие оператора в структуре типа if r.ax $FFFF then....
    На самом деле должно быть if r.ax <> $FFFF then....
    Вероятно, некоторые браузеры (TheBat) воспринимают пару символов <> (паскалевское "не равно") как пустой тег. В IE6, которым пользуюсь я, все выглядит нормально, и я не задумывался о вероятности такого эффекта. Теперь я буду пользоваться специальными символами. Два предыдущих выпуска исправил. Их, если нужно, можно взять на сайте по указанному выше адресу.
  2. Второе замечание касалось возможного выхода за пределы видеопамяти, отвечающей отображаемой на экране. При этом действительно, возможно получение неправильного результата чтения.
    Например, если слово находится в самом левом верхнем углу, то читать слева ничего не надо. Или если заканчивается слово в правом нижнем, то не надо читать правее.
    Если ограничиваться только одной видеостраницей (а текстовых их восемь!), то все просто исправить. И в этой программе я учту это. Даже буду считать, что это именно так.
Есть еще просьба описать работу со всеми прерываниями. Это дело большое, по мере необходимости, буду описывать. Отмечу одно, что такими работами как правило, занимаются те, кто пишет на ассемблере. Я планирую постепенно перевести и Вас на использование ассемблерных вставок. Ничего страшного здесь нет. Дам только несколько ссылок на сайты, где можно найти описание прерываний
  1. Наиболее полное описание прерываний, но на английском, ессно. Автор Ralf Brown
  2. http://cc.bstu.by/~tsv/bezr/ap8.htm
  3. http://os.kaf-i.kg/index.php?chapter=pract&page=9
  4. http://e-dok.narod.ru/msdos/book/gl03-22.html
  5. http://prodos.narod.ru/9.html
  6. Финогенов К.Г. Самоучитель по системным функциям MS-DOS Изд. 3-е Очень интересная книга, всячески рекомендую !
    По этой ссылке можно заказать книгу. У меня есть первое издание. Много почерпнул из него
  7. MS-DOS. РУКОВОДСТВО РАЗРАБОТЧИКА. Электронная книга
Есть еще много других, но это те, которые нашел не тратя больших усилий.

Планы на сейчас

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

поменяем вид курсора на текстовом экране


ОБРАТИТЕ ВНИМАНИЕ, что описанные здесь эффекты будут работать только в полноэкранном режиме. Переключаться между режимами лучше всего с помощью комбинации Alt + ENTER.

Текст программы сильно менять не будем. Просто добавим новую возможность: изменение вида указателя мыши с помощью функции $0A.

Функции 10 ($0A) прерывания $33: Установить маску текстового курсора. Здесь reg - переменная типа REGISTERS
При вызовеAX = $0A
BX - тип указателя. 0 = программный указатель (software). 1 - системный (hardware)
CX - маска И (AND) при BX =0, или начальная линия курсора при BX =1
DX - маска исключающее ИЛИ (XOR) при BX =0, или конечная линия курсора при BX =1
Возвращаетничего
Эта функция позволяет изменить вид указателя мыши на мониторе в текстовом режиме. Она также позволяет выбирать стандартный мерцающий системный (hardware) курсор. К такому Вы привыкли, работая в среде разработки программ Borland Pascal.

По умолчанию указатель отображается как "software". Он имеет вид того символа, над которым находится, но с "инвертированными" атрибутами текста.

Ниже приводится листинг программы, демонстрирующий изменение вида курсора. Вид изменяется при нажатии на левую кнопку мыши: сначала он "стандартный программный" software, потом такой же, но отображается двойным белым крестом на синем фоне, а потом системный (hardware). Попробуйте сами изменять значения r.cx и r.dx для получения различных эффектов.

program mouse_02b;
uses DOS, CRT;

procedure FillScreen;
var f: File of char; c: Char;
begin
  Assign(f, 'mou_02a.pas'); {$I-} Reset(f); {$I+}
  if IOResult <> 0 then Exit;
  ClrScr;
  While NOT EOF(f) and (WhereY < 25) do begin
    Read(f, c); Write(c)
  end;
  Close(f);
end;

var
  r: Registers;
  v: Integer;

procedure ChangeView(ViewNo: Integer);
begin
 r.ax:=$0a;
 case ViewNo of
  1: with r do begin
           bx:=0;
           cx:=$00ff;
           dx:=$4e00 end;
  2: with r do begin
           bx:=0;
           cx:=$0;
           dh:=$1f; dl:=206 end;
  3: with r do begin
           bx:=1;
           cx:=7; dx:=8 end;
 else Exit;
 end;
 intr($33, r); delay(100);
end;


BEGIN

  r.ax:=0;
  intr($33, r);
  if r.ax <> $FFFF then
   begin
     WriteLn('Мышь не обнаружена'); Halt(1)
   end;
  FillScreen;
  ChangeView(1);
  Write('Мышь обнаружена с ',r.bx,' кнопками');
  v:=1;
  r.ax:=1;
  intr($33, r);
  repeat
    r.ax:=3;
    intr($33, r);
    if  r.bx and 1 <> 0 then
    begin
      case v of
       1: v:=2;
       2: v:=3;
       else v:=1 end;
      ChangeView(v);
    end;
  until r.bx and 2 <> 0;
END.
Эта программа "неуклюжая". Часто даже вид курсора проскакиваешь. Из-за этого пришлось ввести задержку в процедуре изменения вида курсора.

Да и драйвер мыши не ориентирован на такой стиль работы: программа опрашивает сама - не случилось ли что?

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

Только очень важно учесть, что эта подпрограмма - обработчик событий от мыши будет вызываться очень часто. Так что ей нельзя поручать ничего медленного. Обычно такая подпрограмма заполняет какие-то глобальные поля и устанавливает флаг "ПРОИЗОШЛО!". Главная программа выполняя свою работу, должна следить за этим флагом. Если он установлен, то в удобное для нее время выполняет нужные действия и снимает флаг. Подпрограмма же, если флаг установлен, ничего не должна делать - еще прежнее сообщение не обработано!

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


ОЧЕНЬ ВАЖНО!!! Подпрограмма, которую будет вызывать драйвер мыши, не простая процедура. Это процедура обслуживает прерывание! ОБЯЗАТЕЛЬНО нужно "присобачить" к ее заголовку ключевое слово FAR;. Для обработчиков других прерываний нужно использовать interrupt, но мышиное прерывание - исключение. Без этого ничего не выйдет!

Правила вызова функции, устанавливающей обработчик прерываний от мыши, описаны в таблице

Функции $0C прерывания $33: Установить обработчик событий от мыши Здесь reg - переменная типа REGISTERS
При вызовеAX = $0C
СX - маска событий (событий, которые Вы хотите получать от мыши)
Номер битаСобытиеУстанавливается командой
0 перемещение мыши reg.CX := reg.CX OR 1;
1нажата левая кнопкаreg.CX := reg.CX OR 2;
2отпущена левая кнопкаreg.CX := reg.CX OR 4;
3нажата правая кнопкаreg.CX := reg.CX OR 8;
4отпущена правая кнопкаreg.CX := reg.CX OR $10;
5нажата центральная кнопкаreg.CX := reg.CX OR $20;
6отпущена центральная кнопкаreg.CX := reg.CX OR $40;
всеВсе событияreg.CX := $007F;
всеУдалить обработчик событийreg.CX := 0;
ES - сегмент адреса Вашей подпрограммы, обработчика событий.
Устанавливается: reg.ES:=Seg(MyMouHandler);
DX - смещение адреса Вашей подпрограммы, обработчика событий.
Устанавливается: reg.DX:=Ofs(MyMouHandler);
Возвращаетничего
Эта функция устанавливает общий (это для системы DOS) обработчик событий от мыши.

В регистре CX задается список событий, по которым Ваша подпрограмма будет вызываться, а полный адрес подпрограммы задается в паре регистровES:DX

Когда возникает одно из определенных битами регистра CX событий, вызывается Ваша подпрограмма и ей передается в регистрах процессора следующая информация:

Еще раз отмечу, что подпрограмме - обработчику прерывания нельзя поручать ничего сложного. Иначе Вы рискуете потерять события. В программе, исходный текст которой, показан ниже, показывается, как должна быть организована работа программы.
program mouse_02b;
uses DOS, CRT;

procedure FillScreen;
var f: File of char; c: Char;
begin
  Assign(f, 'mou_02c.pas'); {$I-} Reset(f); {$I+}
  if IOResult <> 0 then Exit;
  ClrScr;
  While NOT EOF(f) and (WhereY < 25) do begin
    Read(f, c); Write(c)
  end;
  Close(f);
end;

var
  r: Registers;
  v: Integer;
  L_Pressed, R_Pressed: Boolean;
  Flag: Boolean;

procedure MouInt; far;
begin
  if Flag then Exit;
  Flag:=True;
  L_Pressed:=False;
  R_Pressed:=False;
 {Извините, но без ассемблерной вставки сейчас не обойтись:
  Нужно именно сейчас опросить состояние регистров процессора.}

  asm           {Начинается ассемлерная вставка. Далее пойдут команды на языке ассемблера}

    test bx, 1  {проверяем (тестируем) первый бит регистра bx}
    JZ @1  
        {если не установлен, равен 0, то переходим к метке с номером @1
         JZ это обозначение команды "Jump if Zerou"
         Это означает "прыгнуть" (перейти), если ноль}

    mov L_Pressed, 1   
          {Переместить (move по-английски) 1 (единицу) в переменную L_Pressed
           Паскаль поймет 1, как значение "ИСТИНА" (TRUE).
           Если перескочим эту команду, то в переменной останется значение "0" = "ЛОЖЬ" = "FASLE"}
   @1:
           {Теперь аналогично проверим второй бит регистра процессора BX}
    test bx, 2
    jz @2
    mov R_Pressed, 1
   @2:
  end;
  {Ассемблерную вставку, начатую директивой ASM следует завершить END} 
end;


function ChangeView(ViewNo: Integer): Integer;
const
   Current: Integer = -1;
  {И здесь была ошибка в прежней программе}
begin
 ChangeView:=Current;
 if ViewNo = Current then Exit;
 r.ax:=$0a;
 case ViewNo of
  1: with r do begin bx:=0; cx:=$00ff; dx:=$4e00 end;
  2: with r do begin bx:=0; cx:=$0;    dh:=$1f; dl:=ord('+') end;
  3: with r do begin bx:=1; cx:=7; dx:=8 end;
 else Exit;
 end;
 intr($33, r);
end;

BEGIN

  r.ax:=0;
  intr($33, r);
  if r.ax <> $FFFF then
   begin
     WriteLn('Мышь не обнаружена'); Halt(1)
   end;
  FillScreen;

  r.ax:=1; intr($33, r); 
  {Нужно показать мышь на экране. Этого не делалось в неверной версии программы}


  {Установка обработчика}
  r.ax:=$0c;
  r.cx:=2 + 4 + 8;
  {Список событий: 
    - нажата и отпущена левая кнопка;
    - нажата правая}
  r.es:=Seg(MouInt);
  r.dx:=Ofs(MouInt);
  intr($33, r);

  Write('Мышь обнаружена с ',r.bx,' кнопками');
  v:=1;
  repeat
    if Flag then
    begin
      Flag:=False;
      if L_Pressed then
      case v of
        1: v := 1;
        2: v := 3;
        ELSE v := 1
      end;   {Этого END тоже не хватало ... Увы ...  :(( }
      ChangeView(v);
    end;
  until R_Pressed;

 {Удаление обработчика. Так положено}
  r.ax:=$0c;
  r.cx:=0;
  r.es:=Seg(MouInt);
  r.dx:=Ofs(MouInt);
  intr($33, r);
END.

Свои вопросы и предложения присылайте Борису

Мы приглашаем Вас и Ваших друзей к сотрудничеству. Напишите, какая проблема Вас лично интересует - и мы постараемся помочь Вам. Поделитесь со всеми, если Вам удастся найти красивое решение. Присылайте свои программы, и если они хороши, то опубликуем их с обязательным указанием Вашего авторства.

По всем вопросам можно писать либо в Гостевую книгу нашего сайта на www.turbopascal.tk, либо

мне, автору этого выпуска, © Сурину Борису: surin_bp@mail.ru, ICQ: 320096696.

Постараюсь ответить на все вопросы и учесть все разумные предложения

Рассылка поддерживается сайтом www.turbopascal.tk. При перепечатке ссылка на сайт обязательна

Hosted by uCoz