Постановка задачи. Калькулятор внешне будет состоять из трех частей: область ввода, область основных действий (простые арифметические операции) и область дополнительных действий (синус, косинус и т.д.). Все кнопки будут создаваться динамически. Дополнительные действия будут описаны в библиотеках DLL. На основании имеющегося комплекта библиотек будет формироваться внешний вид калькулятора.
Создаем проект. Для начала разместим на форме три Panel с вкладки Standard. Назовем их panelTop, panelMain, panelSpec. На panelTop разместим один Edit с вкладки Standard. Расположение панелей. Для этого в Object Inspector изменим свойство Align у панелей: panelTop – alTop; panelMain – alLeft, 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; BorderIcons – BiMaximize в False; Position в poScreenCenter; назначить на OnShow формы процедуру Edit1Change (для изначального фокуса на Edit1). Также можно кинуть на форму XPManifest с вкладки Win32 для отображения кнопок в стиле Windows XP.
И конечно же добавить больше функций. Это можно сделать аналогично созданию библиотеки для кнопки x^2. В исходниках представлен больший набор дополнительных функций.
В итоге мы получили калькулятор с динамически созданными кнопками, функциональность которого расширена благодаря DLL библиотекам.
Связь со мной: Stanislaw
P.S. Калькулятор можно усовершенствовать добавив: возможность обработки функций принимающих не только одно значение, но и два; обработку нажатия клавиатуры; более удобную обработку исключений (например, с выводом в Label); отслеживание имеющихся функций в реальном времени (например, при активации формы).
Дерзайте, и у вас все получится!