Учебник по программированию.

Первые шаги. Язык программирования PascalABC.

Предыдущий параграф Назад в содержание Следующий параграф


§25. Программа «Калькулятор».

В данном параграфе предлагаю написать программу «Калькулятор». Эта программа будет похожа на калькулятор, который есть в любом Windows-е, в наборе стандартных программ. Соответственно, в ней будут кнопочки, поля, отображающие вводимые числа, и поле для вывода результата. Написав такую программу, мы получим хороший опыт реализации графического интерфейса.

В дальнейшем, если вы станете программистами, то писать кнопочки, поля и вообще весь графический интерфейс, который присущ операционной системе Windows, вам, скорее всего, не придётся, т.к. они есть уже готовые в любом современном языке программирования. Однако сейчас мы пропишем весь графический интерфейс калькулятора «от и до». А так же представим себе, что мы пишем полноценную программу, которая должна перемещаться мышкой по экрану. Всё это не только для того, что бы получить опыт, но и для того, что бы ощутить все преимущества, предоставляемые фирмами разработчиками современных языков.

И так приступим к программированию. Для начала определимся, как должен выглядеть наш калькулятор:

Думаю, что принцип работы калькулятора объяснять не надо, поясню только назначение двух кнопок «<-» кнопка стирает последний введённый символ при вводе числа. Т.е. если пользователь ошибся, то нажимая эту кнопку, он сможет удалять ошибочно введённые символы. Кнопка «С»   всё сбрасывает, что бы можно было снова вводить новые числа и производить над ними новое действие.

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

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

Весь процесс программирования данной программы разделим на четыре этапа. Для начала напишем программу, которая будет просто выводить на экран наш калькулятор, и не будет выполнять ни каких действий. Другими словами выведем графическую оболочку калькулятора на экран. Затем будем добавлять в неё различные процедуры и функции, реализующие графический интерфейс, а после этого добавим процедуры реализующие назначение калькулятора. Последним этапом будет тестирование программы. На этом этапе могут проявиться какие либо ошибки и «глюки», которые необходимо будет устранить.

Рассмотрим все этапы последовательно отдельно друг от друга. Далее будут приведены рассуждения и получившийся код. Код будет приведён только тот, который относится к данному этапу и данным рассуждениям. Соответственно код, который уже написан, приводиться не будет, и будет заменён многоточиями. Настоятельно рекомендую параллельно чтению книги собирать код программы в PascalABC.NET, и тестировать его каждый раз по мере появления нового кода. В таком случае вы прочувствуете на себе весь процесс создания программы и убедитесь, что всё это не пустые слова, а результат реальной работы.

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

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

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

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

Код, написанный на первом этапе, будет следующий:


program Kalkulyator;

uses GraphABC;


type TObyekt = record//Тип объектов управления калькулятора

       x1,y1,x2,y2:integer;//Координаты объекта

       sStr:string; end;//Надпись на кнопке или содержание полей ввода/вывода


const RKn=20;//Размер кнопок


var X0,Y0:integer;//Координаты левого верхнего угла калькулятора

  //Массив объектов управления калькулятором

    Ob:array [1..22] of TObyekt;

  //1..19 - кнопки; 20..22 - поля ввода вывода


procedure UtochneniyeKoordinatOb;//Уточнение координат объектов

var x,y:integer;

begin

  y:=Y0+5;

  x:=X0+5;

  for var i:=1 to 19 do

    begin

       Ob[i].x1:=x; Ob[i].y1:=y;

       Ob[i].x2:=Ob[i].x1+Rkn; Ob[i].y2:=Ob[i].y1+RKn;

       x:=x+RKn+5;

       if (i mod 4) = 0 then

         begin

           y:=y+RKn+5;

           x:=X0+5

         end

    end;

  Ob[19].x2:=Ob[19].x2+5+Rkn;

  for var i:=20 to 22 do

    begin

      Ob[i].x1:=X0+140; Ob[i].y1:=Y0+5+Round((i-19)*120/3-120/3+5);

      Ob[i].x2:=X0+295; Ob[i].y2:=Y0+5+Round((i-19)*120/3-5);

    end;

end;


procedure DrawPolya;

begin

  SetBrushColor(clWhite);

  for var i:= 20 to 22 do

    Rectangle(Ob[i].x1,Ob[i].y1,Ob[i].x2,Ob[i].y2);

end;


procedure DrawKalkulator;

begin

//Рисуем контур калькулятора

  SetBrushColor(clLightGray);

  Rectangle(X0,Y0,X0+300,Y0+130);

  Line(X0+105,Y0,X0+105,Y0+130);

//Уточняем координаты кнопок и полей

  UtochneniyeKoordinatOb;

//Рисуем кнопки

  SetBrushColor(clAzure);

  for var i:=1 to 19 do

    begin

      Rectangle(Ob[i].x1,Ob[i].y1,Ob[i].x2,Ob[i].y2);

      TextOut(Ob[i].x1+Round((Ob[i].x2-Ob[i].x1)/2-

           TextWidth(Ob[i].sStr)/2),Ob[i].y1+2,Ob[i].sStr);

    end

//Рисуем поля для ввода/вывода чисел

  DrawPolya;

end;


procedure ClearKalkulator;

begin

  SetBrushColor(clWhite);

  FillRectangle(X0,Y0,X0+300,Y0+130);

end;


//Тело основной программы

begin

//Инициализируем начальные параметры

  X0:=100; Y0:=10;

  SetPenColor(clBlue); //Цвет контуров калькулятора и объектов

//Заполняем строки в массиве объектов управления

  Ob[1].sStr:='7';   Ob[2].sStr:='8';    Ob[3].sStr:='9';  Ob[4].sStr:='/';

  Ob[5].sStr:='4';   Ob[6].sStr:='5';    Ob[7].sStr:='6';  Ob[8].sStr:='*';

  Ob[9].sStr:='1';   Ob[10].sStr:='2';   Ob[11].sStr:='3'; Ob[12].sStr:='-';

  Ob[13].sStr:='0';  Ob[14].sStr:='+/-'; Ob[15].sStr:='.'; Ob[16].sStr:='+';

  Ob[17].sStr:='<-'; Ob[18].sStr:='C';   Ob[19].sStr:='=';

  Ob[20].sStr:='';   Ob[21].sStr:='';    Ob[22].sStr:='';

//Рисуем калькулятор

  DrawKalkulator;

end.


Этап второй, элементы графического интерфейса.

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


//Определяет, над каким объектом произошло событие нажатия или отпускания

function OprOb(x,y:integer):byte;

begin

  Result:=0;//Событие произошло не над калькулятором

//Если событие произошло над кальклятором, но не над объектом

  if (x>=X0) and (x<=X0+300) and (y>=Y0) and (y<=Y0+130) then Result:=23;

//Если событие произошло над калькулятором

  for var i:=1 to 22 do

    if (x>Ob[i].x1) and (y>Ob[i].y1) and

       (x<Ob[i].x2) and (y<Ob[i].y2) then Result:=i;

end;


Перетаскивание калькулятора по экрану. Создадим переменную с именем Peretaskivaniye, которая будет хранить положение стрелки мыши в момент нажатия на калькуляторе (поля dx и dy) и будет иметь поле логического типа Najatiye, которое будет переводиться в True если нажатие произошло на калькуляторе (функция OprOb выдаст 23) и переводиться в False каждый раз как будет клавиша отпускаться. При перемещении мышки по экрану, если Peretaskivaniye.Najatiye=True, то калькулятор будет перемещаться.

Далее получившийся код:


..................................

Var Peretaskivaniye:record //Переменная для реализации перетаскивания

                            //калькулятора по экрану

            Najatie:boolean;//Произошло ли нажатие на калькуляторе

         dx,dy:integer; end;//Положение мышки в момент нажатия


...............................................

procedure MouseDown(x,y,mb:integer);

var NajOb:byte;//Номер нажатой клавиши

begin

  if mb=1 then //Если нажата левая кнопка мыши

    begin

      NajOb:=OprOb(x,y);

      if NajOb=23 then

        begin

          Peretaskivaniye.Najatie:=true;

          Peretaskivaniye.dx:=x-X0;

          Peretaskivaniye.dy:=y-Y0;

        end;

    end;

end;


procedure MouseUp(x,y,mb:integer);

begin

  Peretaskivaniye.Najatie:=false;

end;


procedure MouseMove(x,y,mb:integer);

begin

  if Peretaskivaniye.Najatie then

    begin

      ClearKalkulator;

      X0:=x-Peretaskivaniye.dx;

      Y0:=y-Peretaskivaniye.dy;

      DrawKalkulator;

    end;

end;


//Тело основной программы

begin

......................................

//Привязываем события к обработчикам

  OnMouseDown:=MouseDown;

  OnMouseUp:=MouseUp;

  OnMouseMove:=MouseMove;

end.


Подсвечивание нажатой клавиши. Так же как и в предыдущем случае, создадим переменную Podsvechivaniye, которая будет содержать в себе поле Ob номер объекта на котором произошло нажатие, и поле Najatiye логического типа, которое переводиться в True, если произошло нажатие на объекте и в False  всегда при отпускании кнопки мыши.

Далее код:


.....................................

Var Podsvechivaniye:record    

            Ob:byte;//Номер объекта на котором было нажатие

      Najatiye:boolean;end;//Если объект подсвечен, то True

.....................................

procedure MouseDown(x,y,mb:integer);

var NajOb:byte;//Номер нажатой клавиши

begin

if mb=1 then //Если нажата левая кнопка мыши

    begin

      ...............................................

      if (NajOb>=1) and (NajOb<=19) then//Подсвечиваем кнопки

        begin

          Podsvechivaniye.Najatiye:=true;

          Podsvechivaniye.Ob:=NajOb;

          SetBrushColor(clLightBlue);

          Rectangle(Ob[Podsvechivaniye.Ob].x1,Ob[Podsvechivaniye.Ob].y1,

                Ob[Podsvechivaniye.Ob].x2,Ob[Podsvechivaniye.Ob].y2);

          TextOut(Ob[Podsvechivaniye.Ob].x1+

           Round((Ob[Podsvechivaniye.Ob].x2-Ob[Podsvechivaniye.Ob].x1)/2-

                  TextWidth(Ob[Podsvechivaniye.Ob].sStr)/2),

                Ob[Podsvechivaniye.Ob].y1+2,Ob[Podsvechivaniye.Ob].sStr);

        end;

    end;

end;


procedure MouseUp(x,y,mb:integer);

begin

  ...............................

  if Podsvechivaniye.Najatiye then//Убираем подсветку с кнопки

    begin

      Podsvechivaniye.Najatiye:=false;

      SetBrushColor(clAzure);

      Rectangle(Ob[Podsvechivaniye.Ob].x1,Ob[Podsvechivaniye.Ob].y1,

                Ob[Podsvechivaniye.Ob].x2,Ob[Podsvechivaniye.Ob].y2);

      TextOut(Ob[Podsvechivaniye.Ob].x1+Round((Ob[Podsvechivaniye.Ob].x2-

                                     Ob[Podsvechivaniye.Ob].x1)/2-

                             TextWidth(Ob[Podsvechivaniye.Ob].sStr)/2),

                Ob[Podsvechivaniye.Ob].y1+2,Ob[Podsvechivaniye.Ob].sStr);

    end;

end;

............................................


Заполнение полей ввода вывода. Числа, над которыми мы будем производить вычисления, будут содержаться в поле sStr в 20-ом и 21-ом элементах массива Ob. В 22-ом элементе будет содержаться результат вычислений. Поле sStr имеет тип String, поэтому при его заполнении  необходимо контролировать вводимые данные, т.к. при переводе её в тип Real функцией StrToFloat может возникнуть ошибка. В нашем случае при нажатии цифровых кнопок ничего другого не будет введено кроме этой цифры. Однако пользователь может нажать несколько раз на точку. Соответственно если была введена уже одна точка, то вторая уже не должна быть введена. Так же необходимо сказать, что в нашем поле может содержаться не более 20 символов. Этот аспект тоже необходимо контролировать. Добавление нового символа в строку будем производить при отпускании левой клавиши мыши.

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

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

Для реализации вышесказанного, создадим переменную с именем Pole, которое будет содержать поле TekPole (текущее поле) номер текущего поля, Tochka1 и Tochka2 логического типа True если точка в поле уже была ввдена.

Так же можно сделать вывод, что необходимо переписать процедуру DrawPolya.

Далее получившийся код:


var Pole: record//Переменная для заполнения полей

      TekPole:=1;//Содержит текущее поле ввода

      Tochka1:=false;//Есть точка в первом поле нет

      Tochka2:=false;//Есть точка во втором поле нет

      end;

.........................................

procedure DrawPolya;

begin

  for var i:= 20 to 22 do

    begin

      if (((Pole.TekPole=1) and (i=20))

         or  ((Pole.TekPole=2) and (i=21)))

           and (i<>22) then SetBrushColor(clLightBlue)

        else SetBrushColor(clWhite);

      Rectangle(Ob[i].x1,Ob[i].y1,Ob[i].x2,Ob[i].y2);

      TextOut(Ob[i].x2-2-TextWidth(Ob[i].sStr),Ob[i].y1+7,Ob[i].sStr);

    end;

end;

......................................

procedure MouseDown(x,y,mb:integer);

var NajOb:byte;//Номер нажатой клавиши

begin

  if mb=1 then //Если нажата левая кнопка мыши

    begin

      NajOb:=OprOb(x,y);

      ..................................

    //Выбираем текущее поле   

      if (NajOb=20) then Pole.TekPole:=1;

      if (NajOb=21) then Pole.TekPole:=2;

      if (NajOb=20) or (NajOb=21) then DrawPolya//Перерисовываем поля

    end;

end;


procedure ZapPolya(ch:char);//Заполняем поля ввода

var i:byte;//Номер элемента для ввода

begin

//Если точки в полях уже есть, то выходим из процедуры

  if Pole.Tochka1 and (Pole.TekPole=1) and (ch = '.') then exit;

  if Pole.Tochka2 and (Pole.TekPole=2) and (ch = '.') then exit;

//Добавляем симол в поле

  if Pole.TekPole=1 then i:=20 else i:=21;

//Если нажали кнопку удаления последнего введённого символа 

  if ch='d' then

    begin

      if Length(Ob[i].sStr)=1 then exit;

      if Ob[i].sStr[Length(Ob[i].sStr)]='.' then

        if Pole.TekPole=1 then Pole.Tochka1:=false

         else Pole.Tochka2:=false;

      Delete(Ob[i].sStr,Length(Ob[i].sStr),1);

      if Length(Ob[i].sStr)=1 then Ob[i].sStr[1]:=' ';

      DrawPolya;

      exit;

    end;

//Если в поле больше символ не войдёт 

  if Length(Ob[i].sStr)>=20 then exit;

  if ch='.' then //Если нажали точку

    if Pole.TekPole=1 then Pole.Tochka1:=true else Pole.Tochka2:=true;

  if ch='-' then //Если нажали кнопку смены знака

     if Ob[i].sStr[1]='-' then Ob[i].sStr[1]:=' ' else Ob[i].sStr[1]:='-'

   else Ob[i].sStr:=Ob[i].sStr+ch; //Если нажали не точку

//Если получилось так, что перед точкой нет символа

  if Ob[i].sStr[2]='.' then Insert('0',Ob[i].sStr,2);

   DrawPolya;

end;


procedure MouseUp(x,y,mb:integer);

begin

  ................................

  if mb=1 then

    begin

//Обрабатываем нажатия кнопок

     var NajOb:=OprOb(x,y);

      case NajOb of

//Заполняем поля ввода

        1:ZapPolya('7');

        2:ZapPolya('8');

        3:ZapPolya('9');

        5:ZapPolya('4');

        6:ZapPolya('5');

        7:ZapPolya('6');

        9:ZapPolya('1');

        10:ZapPolya('2');

        11:ZapPolya('3');

        13:ZapPolya('0');

        14:ZapPolya('-');

        15:ZapPolya('.');

        17:ZapPolya('d');//Нажата кнопка удаления

                         //последнего введённого символа

      end;

    end;

end;

..........................................................


Здесь необходимо сказать, что первый символ полей sStr объектов ввода используется под знак. И ещё то, что для того, что бы программа работала корректно, в теле основной программы, в разделе инициализации необходимо строковым полям 20-ого 21-ого и 22-ого объектов необходимо присвоить значение символа пробел:


Ob[20].sStr:=' ';   Ob[21].sStr:=' ';    Ob[22].sStr:=' ';


Этап третий. Реализация функций калькулятора. На данном этапе нам необходимо обработать нажатия на оставшиеся кнопки кнопки арифметических действий, кнопка «=» (равно) и кнопка «С» (сброс). Рассмотрим их отдельно друг от друга.

Обработка нажатия кнопок арифметических действий. Создадим переменную с именем Deystviye, которая будет символьного типа и будет содержать выбранное действие. Если действие не выбрано, то она будет содержать пробел. При нажатии кнопок с действиями, данная переменная должна принять определённое значение, и на экране должно появиться это действие между полями ввода. Для этого напишем процедуру вывода действия DrawDeyst, которая так же будет вызываться в процедуре DrawKalkulator.

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

Далее получившийся код:


.....................................

var Deystviye:=' ';//Содержит выбранное действие     

..................................

procedure DrawDeyst;//Выводим на экран действие

begin

  SetBrushColor(clLightGray);

  FillRect(X0+125,Y0+37,X0+125+15,Y0+37+15);//Стираем старое

  TextOut(X0+125,Y0+37,Deystviye)//Выводим новое

end;


procedure DrawKalkulator;

begin

..................................

//Рисуем выбранное действие

  DrawDeyst;

..................................

end;

..................................

procedure MouseUp(x,y,mb:integer);

begin

...................................

  if mb=1 then

    begin

//Обрабатываем нажатия кнопок

      var NajOb:=OprOb(x,y);

      case NajOb of

//Выбор нужного действия и вывод его на экран

        4:Deystviye:='/';

        8:Deystviye:='*';

        12:Deystviye:='-';

        16:Deystviye:='+';

      end;

    if (NajOb=4) or (NajOb=8) or (NajOb=12) or (NajOb=16) then

      begin

        DrawDeyst;

        if Pole.TekPole=1 then

          begin

            Pole.TekPole:=2;

            DrawPolya;

          end

      end;

    end;

end;

...................................


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

После вывода результата необходимо заблокировать обработку всех нажатий до нажатия кнопки сброс, что бы случайно не изменить введённые числа и действие над ними. Т.е. пользователь должен видеть весь пример в неизменном виде. Так же нужно будет убрать подсвечивание текущего поля. Для этого создадим переменную с именем Blokirovka логческого типа. При нажатии кнопки равно она будет равна True, при нажатии кнопки cброс False. Так же необходимо будет вставить в код программы отмену обработок нажатий, если Blokirovka = True и изменить процедуру DrawPolya.

Так как мы должны предусмотреть возможность работы калькулятора с помощью клавиатуры, то напишем отдельную процедуру обработки нажатия клавиш равно и сброс с именами Ravno и Sbros соответственно.

Код:

....................................

var Blokirovka :=false;//Блокирует ввод данных после нажатия равно     

...................................

procedure DrawPolya;

begin

  for var i:= 20 to 22 do

    begin

      if (((Pole.TekPole=1) and (i=20))

                    or  ((Pole.TekPole=2) and (i=21)))

      and (i<>22) and (Pole.TekPole<>3)then SetBrushColor(clLightBlue)

        else SetBrushColor(clWhite);

      Rectangle(Ob[i].x1,Ob[i].y1,Ob[i].x2,Ob[i].y2);

      TextOut(Ob[i].x2-2-TextWidth(Ob[i].sStr),Ob[i].y1+7,Ob[i].sStr);

    end;

end;

...........................................

procedure MouseDown(x,y,mb:integer);

var NajOb:byte;//Номер нажатой клавиши

begin

  if mb=1 then //Если нажата левая кнопка мыши

    begin

      ............................................

    //Выбираем текущее поле   

      if Blokirovka then exit;

      .........................................

    end;

end;

....................................

procedure Ravno;

var r1,r2,r3:real;

begin

  r1:=StrToFloat(Ob[20].sStr);

  r2:=StrToFloat(Ob[21].sStr);

  case Deystviye of

   '/':r3:=r1/r2;

   '*':r3:=r1*r2;

   '-':r3:=r1-r2;

   '+':r3:=r1+r2;

  end;

  Ob[22].sStr:=FloatToStr(r3);

  Blokirovka:=true;

  Pole.TekPole:=3;

  SetBrushColor(clLightGray);

  TextOut(X0+125,Y0+78,'=');

  DrawPolya;

end;


procedure Sbros;

begin

  Blokirovka:=false;

  Pole.TekPole:=1;

  Deystviye:=' ';

  for var i:=20 to 22 do Ob[i].sStr:=' ';

  DrawPolya;

  DrawDeyst;

//Стираем равн0

  SetBrushColor(clLightGray);

  TextOut(X0+125,Y0+78,'  ');

end;


procedure MouseUp(x,y,mb:integer);

begin

  .............................

  if mb=1 then

    begin

//Обрабатываем нажатия кнопок   

      var NajOb:=OprOb(x,y);

      if Blokirovka and (NajOb<18) then Exit;

      case NajOb of

        ....................

//Обработка нажатия кнопки равно  

        19:Ravno;

//Обработка кнопки Сброс

        18:Sbros;       

      end;

      ........................     

    end;

end;

..............................


Последний этап. Тестируем программу.

В результате тестирования обнаружились некоторые ошибки. Далее будут приведены их словесные описания и способ их устранения.

1. Если в поле ввода ещё нет ни какого числа, то при нажатии кнопки смены знака выходит ошибка.

Для устранения данной ошибки не будем вспоминать или снова вникать в процедуру ZapPolya и переписывать её заново, а просто вставим следующие строчки:


..................................

//Добавляем симол в поле

  if Pole.TekPole=1 then i:=20 else i:=21;

//Если ничего нет в строке и нажата кнопка смены знака

  if (ch='-') and (Length(Ob[i].sStr)=1) then

    begin

     if Ob[i].sStr[1]='-' then Ob[i].sStr[1]:=' '

        else Ob[i].sStr[1]:='-';

     DrawPolya;

     exit;

    end;

//Если нажали кнопку удаления последнего введённого символа 

..................................


2. Если нажать одну кнопку калькулятора и, не отпуская кнопки мыши, перетащить её на вторую кнопку, то обработается нажатие второй кнопки.

У нас в программе есть две процедуры MouseDown и MouseUp. В обоих есть локальная переменная NajOb, в которой содержиться номер нажатого объекта. Для устранения данной ошибки сделаем одну такую переменную, которая будет глобальной, т.е. опишем её в разделе описания переменных самой программы. Из процедур описание данных локальных переменных необходимо удалить. Думаю, вы сами без труда это сделаете, поэтому код приводить не буду.

3. Если после того, как нажали кнопку «=» перетаскивать калькулятор, то знак равно между полями ввода и полем вывода исчезает.

Для устранения ошибки вставим в процедуру DrawKalkulator следующий код:


..................................

//Рисуем выбранное действие

  DrawDeyst;

//Если нажали равно, то рисуем знак равно

  if Blokirovka then  TextOut(X0+125,Y0+78,'=');

..................................


4. Если не введено число или не выбрано действие, то при нажатии кнопки равно выходит ошибка.

Устраним эту ошибку следующим образом: в процедуре Ravno, прежде чем производить какие либо действия проверим, все ли поля заполнены и выбрано ли действие:


procedure Ravno;

var r1,r2,r3:real;

begin

//Если не выбрано действие или не введено число, то выйти 

  if (Deystviye=' ') or (Length(Ob[20].sStr)=1) or (Length(Ob[21].sStr)=1)

    then exit;

......................................


5.Если в каком либо поле ввода уже находилась точка, то после нажатия кнопки равно, а затем кнопки сброс, точку в это поле уже ввести невозможно. Это потому, что в процедуре Sbros забыли сбросить переменные Pole.Tochka1 и Pole.Tochka2:


procedure Sbros;

begin

  Blokirovka:=False;

  Pole.TekPole:=1;

  Pole.Tochka1:=false;

  Pole.Tochka2:=false;

......................


На этом  разработку калькулятора мы закончим. Можно добавить ещё возможность работы с клавиатуры. Это мы сделаем в разделе решения задач.

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

Работая над калькулятором, я приводил код порциями, которые отвечают за то или иное действие. Но даже в таком виде, на мой взгляд, не сразу можно понять, как он работает. А если вам попадётся код подобной программы вообще без комментариев и целиком. И нужно будет его доработать или исправить ошибки. Думаю, в таком случае вам будет проще написать программу заново.


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


Задача.

Дописать в программе калькулятор возможность работы с клавиатуры.


Решение.

.................................

procedure KeyPress(ch:char);

begin

//Убираем подсветку с кнопки

  if Podsvechivaniye.Najatiye then//Убираем подсветку с кнопки

    begin

      Podsvechivaniye.Najatiye:=false;

      SetBrushColor(clAzure);

      Rectangle(Ob[Podsvechivaniye.Ob].x1,Ob[Podsvechivaniye.Ob].y1,

                Ob[Podsvechivaniye.Ob].x2,Ob[Podsvechivaniye.Ob].y2);

      TextOut(Ob[Podsvechivaniye.Ob].x1+Round((Ob[Podsvechivaniye.Ob].x2-

                                     Ob[Podsvechivaniye.Ob].x1)/2-

                             TextWidth(Ob[Podsvechivaniye.Ob].sStr)/2),

             Ob[Podsvechivaniye.Ob].y1+2,Ob[Podsvechivaniye.Ob].sStr);

    end;

//Определяем нажатый объект

  case ch of

    '0':NajOb:=13;

    '1':NajOb:=9;

    '2':NajOb:=10;

    '3':NajOb:=11;

    '4':NajOb:=5;

    '5':NajOb:=6;

    '6':NajOb:=7;

    '7':NajOb:=1;

    '8':NajOb:=2;

    '9':NajOb:=3;

    '/':NajOb:=4;

    '*':NajOb:=8;

    '-':NajOb:=12;

    '+':NajOb:=16;

    '.':NajOb:=15;

    '=':NajOb:=19;

   else NajOb:=0;

   end;

//Подсвечиваем кнопки

  if (NajOb>=1) and (NajOb<=19) then

      begin

        Podsvechivaniye.Najatiye:=true;

        Podsvechivaniye.Ob:=NajOb;

        SetBrushColor(clLightBlue);

        Rectangle(Ob[Podsvechivaniye.Ob].x1,Ob[Podsvechivaniye.Ob].y1,

              Ob[Podsvechivaniye.Ob].x2,Ob[Podsvechivaniye.Ob].y2);

        TextOut(Ob[Podsvechivaniye.Ob].x1+

           Round((Ob[Podsvechivaniye.Ob].x2-Ob[Podsvechivaniye.Ob].x1)/2-

                     TextWidth(Ob[Podsvechivaniye.Ob].sStr)/2),

               Ob[Podsvechivaniye.Ob].y1+2,Ob[Podsvechivaniye.Ob].sStr);

      end;

  if Blokirovka and (NajOb<18) then Exit;

      case NajOb of

//Заполняем поля ввода

        1:ZapPolya('7');

        2:ZapPolya('8');

        3:ZapPolya('9');

        5:ZapPolya('4');

        6:ZapPolya('5');

        7:ZapPolya('6');

        9:ZapPolya('1');

        10:ZapPolya('2');

        11:ZapPolya('3');

        13:ZapPolya('0');

        15:ZapPolya('.');

//Выбор нужного действия и вывод его на экран

        4:Deystviye:='/';

        8:Deystviye:='*';

        12:Deystviye:='-';

        16:Deystviye:='+';

//Обработка нажатия кнопки равно  

        19:Ravno;

      end;

//Меняем текущее поле

    if (NajOb=4) or (NajOb=8) or (NajOb=12) or (NajOb=16) then

      begin

        DrawDeyst;

        if Pole.TekPole=1 then

          begin

            Pole.TekPole:=2;

            DrawPolya;

          end

      end;

end;


procedure KeyUp(Key:integer);

begin

//Убираем подсветку с кнопки

  if Podsvechivaniye.Najatiye then//Убираем подсветку с кнопки

    begin

      Podsvechivaniye.Najatiye:=false;

      SetBrushColor(clAzure);

      Rectangle(Ob[Podsvechivaniye.Ob].x1,Ob[Podsvechivaniye.Ob].y1,

                Ob[Podsvechivaniye.Ob].x2,Ob[Podsvechivaniye.Ob].y2);

      TextOut(Ob[Podsvechivaniye.Ob].x1+Round((Ob[Podsvechivaniye.Ob].x2-

                        Ob[Podsvechivaniye.Ob].x1)/2-     

              TextWidth(Ob[Podsvechivaniye.Ob].sStr)/2),

                Ob[Podsvechivaniye.Ob].y1+2,Ob[Podsvechivaniye.Ob].sStr);

    end;

end;


procedure KeyDown(Key:integer);

begin

//Убираем подсветку с кнопки

  if Podsvechivaniye.Najatiye then//Убираем подсветку с кнопки

    begin

      Podsvechivaniye.Najatiye:=false;

      SetBrushColor(clAzure);

      Rectangle(Ob[Podsvechivaniye.Ob].x1,Ob[Podsvechivaniye.Ob].y1,

                Ob[Podsvechivaniye.Ob].x2,Ob[Podsvechivaniye.Ob].y2);

      TextOut(Ob[Podsvechivaniye.Ob].x1+Round((Ob[Podsvechivaniye.Ob].x2-

                                     Ob[Podsvechivaniye.Ob].x1)/2-

               TextWidth(Ob[Podsvechivaniye.Ob].sStr)/2),

                Ob[Podsvechivaniye.Ob].y1+2,Ob[Podsvechivaniye.Ob].sStr);

    end;

//Определяем нажатый объект

  case key of

    8:NajOb:=17;

   13:NajOb:=19;

    67:NajOb:=18;

    83:NajOb:=14;

   else NajOb:=0;

   end;

//Подсвечиваем кнопки

  if (NajOb>=1) and (NajOb<=19) then

      begin

        Podsvechivaniye.Najatiye:=true;

        Podsvechivaniye.Ob:=NajOb;

        SetBrushColor(clLightBlue);

        Rectangle(Ob[Podsvechivaniye.Ob].x1,Ob[Podsvechivaniye.Ob].y1,

              Ob[Podsvechivaniye.Ob].x2,Ob[Podsvechivaniye.Ob].y2);

        TextOut(Ob[Podsvechivaniye.Ob].x1+

                Round((Ob[Podsvechivaniye.Ob].x2-

                                   Ob[Podsvechivaniye.Ob].x1)/2-

                               TextWidth(Ob[Podsvechivaniye.Ob].sStr)/2),

                Ob[Podsvechivaniye.Ob].y1+2,Ob[Podsvechivaniye.Ob].sStr);

      end;

  if Blokirovka and (NajOb<>18) then Exit;

      case NajOb of

//Заполняем поля ввода

        14:ZapPolya('-');

//Нажата кнопка удаления последнего введённого символа

        17:ZapPolya('d');

//Обработка нажатия кнопки равно  

        19:Ravno;

//Обработка кнопки Сброс

        18:Sbros;       

      end;

end;


//Тело основной программы

begin

.................................

  OnKeyPress:=KeyPress;

  OnKeyUp:=KeyUp;

  OnKeyDown:=KeyDown;

end.


Далее приведу описание работы калькулятора с клавиатуры. Ввод символов обозначающих цифры, точку и действия равносильно нажатиям соответствующих кнопок на экранном калькуляторе. Кнопке равно соответствует ввод символа «=» и клавиши Enter. Кнопке сброс соответствует клавиша с символом «С». Кнопке смены знака клавиша с символом «S».

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

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

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


Предыдущий параграф Назад в содержание Следующий параграф