Постановка задачи. Калькулятор внешне будет состоять из трех частей: область ввода, область основных действий (простые арифметические операции) и область дополнительных действий (синус, косинус и т.д.). Все кнопки будут создаваться динамически. Дополнительные действия будут описаны в библиотеках DLL. На основании имеющегося комплекта библиотек будет формироваться внешний вид калькулятора.

Создаем проект. Для начала разместим на форме три Panel с вкладки Standard. Назовем их panelTop, panelMain, panelSpec. На panelTop разместим один Edit с вкладки Standard. Расположение панелей. Для этого в Object Inspector изменим свойство Align у панелей: panelTopalTop; panelMainalLeft, panelSpec – alClient. ClientHeight у формы выставим в 225 (значение выбранное мной).

Добавим в раздел uses модули

uses
 //остальные модули
 Buttons, ImageHlp;

Первый необходим для добавления кнопок, второй для получения списка функций из DLL файла.

Теперь необходимо произвести отрисовку на панели области основных действий. Для названий кнопок создадим массив названий кнопок.

const //массив для заполнения Caption кнопок
 bCaptions: array[0..19] of string =
 ('BS','7','4','1','0','CE','8','5','2',',',
 'C','9','6','3','=','±','/','*','-','+');

Отрисовка будет производиться сверху вниз, слева направо, по 5 кнопок в столбце.

В обработчике формы OnCreate (вкладка Events в Object Inspector) пишем код.

procedure TForm1.FormCreate(Sender: TObject);
var
 i,n1,n2:Integer;
 sb: TSpeedButton;
begin
 //для отображения Edit на всю панель
 edit1.Align := alClient;

 //отрисовка главной панели
 n1:=0; n2:=0;
 for i := 0 to length(bCaptions)-1 do
 begin
 if i>0 then if (i mod 5)=0 then begin inc(n1); n2:=0; end;
 sb := TSpeedButton.Create(form1);
 sb.Parent := panelMain;
 sb.Font.Name := 'Consolas';
 sb.Font.Height := 18;
 sb.Name := 'sbMain' + inttostr(i);
 sb.Left := 40*n1+10;
 sb.Top := n2*35+10;
 inc(n2);
 sb.Height:=30;
 sb.Caption:=bCaptions[i];
 sb.Width:=35;
 sb.Tag:= i;
 end;
end;

Рассмотрим подробнее, что мы здесь задаем. Отрисовка происходит в цикле, параметры n1 и n2 необходимы для задания расположения кнопок. Сначала создается кнопка, указывается ее родитель (элемент на котором она будет размещена), задаются шрифты, имя, положение, высота, ширина, caption и tag (пригодится для обработки нажатия).

Можно запустить проект на выполнение. Неплохо для начала. Теперь необходимо задать самое главное – OnClick нажатие на кнопки.

Добавим в раздел var переменные:

 res: Extended; //запоминается промежуточное значение
 deistvie: Integer; //выбор операции
 //проверка на 1 цифру и на нажатие действия(+,-,*,/ и т.д.)
 checkFirst, check: boolean; 

Создадим процедуру bMainClick (для обработки нажатия кнопок основной панели).Добавим в раздел type объявление процедуры:

type
 TForm1 = class(TForm)
 //…
 procedure bMainClick(Sender: TObject);
 private
 //…

Опишем еще несколько процедур и создадим обработчики:

//кнопка очистки
procedure ClearClick;
begin
 Form1.Edit1.Text := '0';
 checkFirst := True;
 res := 0;
 deistvie := 0;
 check := false;
end;

//для кнопок действия(+,-,*,/ и т.д.)
procedure miniClear;
begin
 if check = True then Form1.Edit1.Text:= '';
end;

//основные арифметические операции
function Calculate(number,res: Extended; deistvie: Integer):Extended;
begin
 result:=0;
 case deistvie of
 14: result := number;
 16: result := res / number; // /
 17: result := res * number; // *
 18: result := res - number; // -
 19: result := res + number; // +
 end;
end;

// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 'BS' '7' '4' '1' '0' 'CE' '8' '5' '2' ',' 'C' '9' '6' '3' '=' '±' '/' '*' '-' '+'
procedure TForm1.bMainClick(Sender: TObject);
begin
 try
 checkFirst := False;

 case (Sender as TSpeedButton).Tag of
 4: begin miniClear; if Edit1.Text <> '0' then Edit1.Text:= Edit1.Text + '0'; check := false; end; // 0
 1,2,3,6,7,8,11,12,13:
 begin miniClear; if Edit1.Text='0' then Edit1.Text:= ''; Edit1.Text:= Edit1.Text + (Sender as TSpeedButton).Caption; check := false; end; // 1-9
 10: ClearClick; // C
 9: if check = True then begin Edit1.Text := '0,'; check:= false; end else if pos(',',Edit1.Text)=0 then Edit1.Text:= Edit1.Text+ ','; // ,
 0: if length(Edit1.Text)=1 then ClearClick else Edit1.Text := Copy(Edit1.Text,1,Length(Edit1.Text)-1); // BS
 15: edit1.Text := FloatToStr(StrToFloat(Edit1.Text)*(-1)); // ±
 5: begin Edit1.Text := '0'; checkFirst := True; end; // CE

 16,17,18,19:begin res:= strtofloat(Edit1.Text); deistvie := (Sender as TSpeedButton).Tag; check := True; end;
 14: begin Edit1.text := FloatToStr( Calculate( StrToFloat(Form1.Edit1.text),res,deistvie ) ); deistvie:=14; end;
 end;

 except
 on E:Exception do ShowMessage(e.Message);
 end;
end;

В обработчике формы OnCreate добавим строки:

procedure TForm1.FormCreate(Sender: TObject);
 //…
 sb.Tag:= i;
 sb.OnClick:=bMainClick;
 end;

 deistvie:=14; // =
 res:=0;
 checkFirst := True; 
end;

В описанных выше процедурах идет обращение к тегу кнопок. Подробно на этом останавливаться я не буду, все и так понятно :-)

На OnChange и OnKeyPress Edit1 вставим следующий код:

procedure TForm1.Edit1Change(Sender: TObject);
begin
 Edit1.SetFocus;
 Edit1.SelStart := Length(Edit1.Text);
end;

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
 key:=#0;
end;

Этим мы ставим фокус на Edit1, перемещаем позицию курсора в конец и запрещаем любое нажатие на клавиатуре (я не ставил задачу обработки нажатия кнопок).

Можно испытать калькулятор на работоспособность.

Осталось только реализовать отрисовку дополнительной панели на основе имеющихся DLL библиотек.

Создадим библиотеку для кнопки x^2. Для этого зайдем в File > New > Other > DLL Wizard.

library xkvadrat;
function newFunc(x: Extended):Extended; stdcall;
begin
 result := x*x;
end;
exports
 newFunc name 'x^2';
end.

Сохраняем библиотеку в отдельной папке и компилируем. Получим на выходе файл xkvadrat.dll.

Экспорт библиотеки происходит по имени. Это значение будет использоваться для Caption будущей кнопки.

Для получения этого имени не будем изобретать велосипед, используем процедуру из Интернета:

//получить список функций DLL
procedure ListDLLExports(const FileName: string; List: TStrings);
type
 TDWordArray = array [0..$FFFFF] of DWORD;
var
 imageinfo: LoadedImage;
 pExportDirectory: PImageExportDirectory;
 dirsize: Cardinal;
 pDummy: PImageSectionHeader;
 i: Cardinal;
 pNameRVAs: ^TDWordArray;
 Name: string;
begin
 List.Clear;
 if MapAndLoad(PChar(FileName), nil, @imageinfo, True, True) then
 begin
 try
 pExportDirectory := ImageDirectoryEntryToData(imageinfo.MappedAddress,
 False, IMAGE_DIRECTORY_ENTRY_EXPORT, dirsize);
 if (pExportDirectory <> nil) then
 begin
 pNameRVAs := ImageRvaToVa(imageinfo.FileHeader, imageinfo.MappedAddress,
 DWORD(pExportDirectory^.AddressOfNames), pDummy);
 for i := 0 to pExportDirectory^.NumberOfNames - 1 do
 begin
 Name := PChar(ImageRvaToVa(imageinfo.FileHeader, imageinfo.MappedAddress,
 pNameRVAs^[i], pDummy));
 List.Add(Name);
 end;
 end;
 finally
 UnMapAndLoad(@imageinfo);
 end;
 end;
end;

Также необходима процедура для получения списка имеющихся файлов:

//получить список файлов
procedure GetAllFiles(Path: string; Sl: TStringList);
var
 sRec: TSearchRec;
 isFound: boolean;
begin
 isFound := FindFirst( Path + '\*.*', faAnyFile, sRec ) = 0;
 while isFound do
 begin
 if ( sRec.Name <> '.' ) and ( sRec.Name <> '..' ) then
 begin
 if ( sRec.Attr and faDirectory ) = faDirectory then
 GetAllFiles( Path + '\' + sRec.Name, Sl );
 //для добавления только файлов .dll
 if pos('.dll', sRec.Name)<>0 then
 Sl.Add( Path + '\' + sRec.Name );
 end;
 Application.ProcessMessages;
 isFound := FindNext( sRec ) = 0;
 end;
 FindClose( sRec );
end;

Добавим следующее: в раздел type:

procedure bSpecClick(Sender: TObject);

в var:

 //необходимо для дополнительной панели
 List:TStringList;
 Names:TStringList;

В обработчик OnCreate формы:

 //отрисовка дополнительной панели
 Names := TStringList.Create;
 GetAllFiles('lib', Names);

 if names.Count<>0 then
 begin
 n1:=0; n2:=0;
 for i := 0 to names.Count-1 do
 begin
 if i>0 then if (i mod 5)=0 then begin
 inc(n1); n2:=0;
 end;
 Form1.ClientWidth := panelMain.Width + 55 + 40*(n1);
 List := TStringList.Create;
 ListDLLExports(Names.Strings[i], List);
 sb := TSpeedButton.Create(form1);
 sb.Parent := panelSpec;
 sb.Font.Name := 'Consolas';
 sb.Font.Height := 14;
 sb.Name := 'sbSpec' + inttostr(i);
 sb.Left := 40*n1+10;
 sb.Top := n2*35+10;
 inc(n2);
 sb.Height:=30;
 sb.Width:=35;
 sb.Tag:= i;
 sb.OnClick:=bSpecClick;
 sb.Caption:=list.Strings[0];
 end;
 end else Form1.ClientWidth := panelMain.Width;

Создание кнопок идет аналогично созданию предыдущих. Интерес представляет получение списка файлов и имени функции из DLL. В строке GetAllFiles('lib', Names); мы получаем список DLL файлов из папки lib (необходимо создать эту папку рядом с исполняемым exe файлов, тут будут расположены все библиотеки с дополнительными функциями). Если количество файлов не равно нулю, начинаем создавать кнопки.

В строке ListDLLExports(Names.Strings[i], List); получаем список всех функций из определенного файла. В Caption кнопки передаем значение первой функции sb.Caption:=list.Strings[0];. Размер клиентского окна рассчитывается из выражения

Form1.ClientWidth := panelMain.Width + 55 + 40*(n1);

Это значение получено экспериментальным путем.

Опишем процедуру обработки нажатия кнопок дополнительной панели:

//для нажатий кнопок дополнительной панели
procedure TForm1.bSpecClick(Sender: TObject);
var
 H:THandle;
 NewFunc: function(x: Extended): Extended; stdcall;
begin
 try
 H := LoadLibrary(PChar(Names.Strings[(sender as tspeedbutton).Tag]));
 if H<>0 then
 begin
 @newFunc := GetProcAddress(H,pchar(1));
 edit1.Text := floattostr(newFunc(strtofloat(edit1.Text)));
 FreeLibrary(Handle);
 end
 else ShowMessage('Библиотека ' + copy(Names.Strings[(sender as tspeedbutton).Tag],5,Length(Names.Strings[(sender as tspeedbutton).Tag])-1) + ' не найдена!');
 except
 on E:exception do
 showmessage(e.Message);
 end;
end;

Обработчик bSpecClick представляет собой обращение к определенной кнопке через ее Tag и выполнение функции из библиотеки. В строке @newFunc := GetProcAddress(H,pchar(1)); идет обращение к первой функции в библиотеке.

Теперь осталось только привести форму в порядок: установить BorderStyle в bsSingle; BorderIconsBiMaximize в False; Position в poScreenCenter; назначить на OnShow формы процедуру Edit1Change (для изначального фокуса на Edit1). Также можно кинуть на форму XPManifest с вкладки Win32 для отображения кнопок в стиле Windows XP.

И конечно же добавить больше функций. Это можно сделать аналогично созданию библиотеки для кнопки x^2. В исходниках представлен больший набор дополнительных функций.

В итоге мы получили калькулятор с динамически созданными кнопками, функциональность которого расширена благодаря DLL библиотекам.


Связь со мной: Stanislaw

Исходник калькулятора

P.S. Калькулятор можно усовершенствовать добавив: возможность обработки функций принимающих не только одно значение, но и два; обработку нажатия клавиатуры; более удобную обработку исключений (например, с выводом в Label); отслеживание имеющихся функций в реальном времени (например, при активации формы).

Дерзайте, и у вас все получится!


 Похожие публикации
2016-04-02 • Просмотров [ 559 ]