В этой статье речь пойдет об организации диалоговых окон в Delphi, которые обычно вызываются процедурой ShowMessage и подобными стандартными процедурами. При этом вы получаете стандартное окно такого типа. Например, если вызвать процедуру:
ShowMessage('Это стандартное системно окно сообщения');
При этом мы получим вот такие окно:

Как видите это не очень красивое окно. Его смысл в том, чтобы что-то быстро написать на коленке и не более того. Хотя оно модальное и полностью выполняет свою функцию, но это не то, что хотело бы видеть большинство программистов и пользователей программ.
Давайте сделаем свое окно. Я решил сделать четыре типа окна:
- окно с вопросом для пользователя;
- окно с сообщением для пользователя;
- окно с предложением сохранить измененные на форме данные;
- окно, предупреждающее о том, что производится попытка удаления данных.
Окно с вопросом для пользователя
Обычно в таком окне задается какой-либо вопрос, требующий подтверждения или отказа. Например, такое окно может выглядеть так:

Может выглядеть и по другому. Все зависит от оформления. Сами кнопки могут называться как «Да/Нет» или «Принять/Отменить» и т.п.
В кнопке «Да» напишем простой код:
procedure TfrmQuestion.cmdYesClick(Sender: TObject); begin bModal:=true; Close; end;
В кнопке «Отмена»:
procedure TfrmQuestion.cmdCancelClick(Sender: TObject); begin bModal:=false; Close; end;
И в событии onFromClose:
procedure TfrmQuestion.FormClose(Sender: TObject; var Action: TCloseAction); begin Action:=TCloseAction.caFree; frmQuestion:=nil; end;
Саму форму назовем frmQuestion. Полный код формы:
unit question_; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Effects, FMX.Objects; type TfrmQuestion = class(TForm) cmdCancel: TButton; lblMessage: TLabel; cmdYes: TButton; StyleBook1: TStyleBook; recClient: TRectangle; ShadowEffect1: TShadowEffect; procedure cmdCancelClick(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure cmdYesClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var frmQuestion: TfrmQuestion; implementation {$R *.fmx} uses mdi; procedure TfrmQuestion.cmdCancelClick(Sender: TObject); begin bModal:=false; Close; end; procedure TfrmQuestion.cmdYesClick(Sender: TObject); begin bModal:=true; Close; end; procedure TfrmQuestion.FormClose(Sender: TObject; var Action: TCloseAction); begin Action:=TCloseAction.caFree; frmQuestion:=nil; end; end.
Переменная bModal — это обычная глобальная переменная типа boolean, которую можно объявить в главной форме приложения.
Окно с сообщением для пользователя
Аналогичным образом создается окно с сообщением для пользователя. Назовем его frmMessage:

Полный код данного окна:
unit message_; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Objects, FMX.Effects; type TfrmMessage = class(TForm) cmdClose: TButton; lblMessage: TLabel; StyleBook1: TStyleBook; recClient: TRectangle; ShadowEffect1: TShadowEffect; procedure cmdCloseClick(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private { Private declarations } public { Public declarations } end; var frmMessage: TfrmMessage; implementation {$R *.fmx} procedure TfrmMessage.cmdCloseClick(Sender: TObject); begin Close; end; procedure TfrmMessage.FormClose(Sender: TObject; var Action: TCloseAction); begin Action:=TCloseAction.caFree; frmMessage:=nil; end; end.
Как видим, здесь всего три строчки кода, не считая автоматически сгенерированного средой Delphi.
Окно с предложением сохранить измененные на форме данные
Данное окно назовем frmPost:

Код окна:
unit post_; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Effects, FMX.Objects; type TfrmPost = class(TForm) cmdCancel: TButton; lblMessage: TLabel; cmdYes: TButton; cmdNo: TButton; StyleBook1: TStyleBook; recClient: TRectangle; ShadowEffect1: TShadowEffect; procedure cmdCancelClick(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure cmdYesClick(Sender: TObject); procedure cmdNoClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var frmPost: TfrmPost; implementation {$R *.fmx} uses mdi; procedure TfrmPost.cmdCancelClick(Sender: TObject); begin sPost:='Отмена'; Close; end; procedure TfrmPost.cmdNoClick(Sender: TObject); begin sPost:='Нет'; Close; end; procedure TfrmPost.cmdYesClick(Sender: TObject); begin sPost:='Да'; Close; end; procedure TfrmPost.FormClose(Sender: TObject; var Action: TCloseAction); begin Action:=TCloseAction.caFree; frmPost:=nil; end; end.
Если в первом окне была логическая переменная bModal, то в данном текстовая переменная sPost, которая также может быть объявлена в главной форме приложения.
Окно, предупреждающее о том, что производится попытка удаления данных
Ну и последнее окно — окно с запросом на удаление данных, которое мы назовем frmDelete:

Полный под формы данного окна:
unit delete_; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Effects, FMX.Objects; type TfrmDelete = class(TForm) cmdCancel: TButton; lblMessage: TLabel; cmdDelete: TButton; StyleBook1: TStyleBook; recClient: TRectangle; ShadowEffect1: TShadowEffect; procedure cmdCancelClick(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure cmdDeleteClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var frmDelete: TfrmDelete; implementation {$R *.fmx} uses mdi; procedure TfrmDelete.cmdCancelClick(Sender: TObject); begin bModal:=false; Close; end; procedure TfrmDelete.cmdDeleteClick(Sender: TObject); begin bModal:=true; Close; end; procedure TfrmDelete.FormClose(Sender: TObject; var Action: TCloseAction); begin Action:=TCloseAction.caFree; frmDelete:=nil; end; end.
Организация взаимодействия диалоговых окон
Теперь, когда у нас есть готовые модули четырех типов окон, можем рассмотреть принцип использования.
Использовать перечисленные формы очень просто. В нужный момент их надо просто создавать из кода вот и все. Но при каждом создании желательно указывать координаты их появления. Можно конечно их всегда выводить в центре рабочего стола, но это не эстетично.
Более интересно и красиво, когда они выводятся в центре того окна, из которого они вызываются. А еще под них можно сделать затемненную подложку. То есть между формой, из которой вызывается модальное окно и самим модальным окном сделаем затемненную подложку — полупрозрачную форму без рамок. Мы назовем ее frmGray и у нее будет всего одна процедура onFormClose:
unit gray_; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects; type TfrmGray = class(TForm) Rectangle1: TRectangle; procedure FormClose(Sender: TObject; var Action: TCloseAction); private { Private declarations } public { Public declarations } end; var frmGray: TfrmGray; implementation {$R *.fmx} procedure TfrmGray.FormClose(Sender: TObject; var Action: TCloseAction); begin Action:=TCloseAction.caFree; frmGray:=nil; end; end.
В конструкторе форм мы лишь зададим данной форме полупрозрачный цвет. Это вы можете сделать на свое усмотрение, а потом подрегулировать. Также мы укажем у свойства FormStyle значение Normal и WindowState = wsMaximized. Не забудьте убрать рамку формы.
Затем мы создадим модуль dialogs_:
unit dialogs_; interface uses Windows, Messages, FMX.Forms, SysUtils, gray_, question_, mdi, message_, post_, delete_, UITypes; procedure QuestionCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); procedure MessageCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); procedure PostCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); procedure DeleteCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); implementation procedure QuestionCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); begin Application.CreateForm(TfrmGray,frmGray); frmGray.Show; Application.CreateForm(TfrmQuestion, frmQuestion); frmQuestion.Left:=Round(Left +(Width-frmQuestion.Width)/2); frmQuestion.Top:=Round(Top +(Height-frmQuestion.Height)/2+20); frmQuestion.lblMessage.Text:=Text; frmQuestion.ShowModal; frmGray.Close; end; procedure MessageCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); begin Application.CreateForm(TfrmGray,frmGray); frmGray.Show; Application.CreateForm(TfrmMessage, frmMessage); frmMessage.Left:=Round(Left +(Width-frmMessage.Width)/2); frmMessage.Top:=Round(Top +(Height-frmMessage.Height)/2+20); frmMessage.lblMessage.Text:=Text; frmMessage.ShowModal; frmGray.Close; end; procedure PostCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); begin Application.CreateForm(TfrmGray,frmGray); frmGray.Show; Application.CreateForm(TfrmPost, frmPost); frmPost.Left:=Round(Left +(Width-frmPost.Width)/2); frmPost.Top:=Round(Top +(Height-frmPost.Height)/2+20); frmPost.lblMessage.Text:=Text; frmPost.ShowModal; frmGray.Close; end; procedure DeleteCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); begin Application.CreateForm(TfrmGray,frmGray); frmGray.Show; Application.CreateForm(TfrmDelete, frmDelete); frmDelete.Left:=Round(Left +(Width-frmDelete.Width)/2); frmDelete.Top:=Round(Top +(Height-frmDelete.Height)/2+20); frmDelete.lblMessage.Text:=Text; frmDelete.ShowModal; frmGray.Close; end; end.
Обратите внимание на процедуры этого модуля:
procedure QuestionCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); procedure MessageCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); procedure PostCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String); procedure DeleteCreate(Left: integer; Top: integer; Height: integer; Width: integer; Text: String);
В каждую из этих процедур передаются габариты и координаты формы, из которой вызывается одно из четырех наших модальных окон. Конечный параметр Text является передаваемой текстовой строкой — сообщение, которое будет появляться в центре окна.
Здесь в секции uses обязательно должны быть подключены все наши создаваемые ранее модули.
Теперь посмотрим на несколько примеров того, как мы можем вызывать свои созданные модальные окна вместо системных.
Например, у нас есть форма frmRacks и мы собираемся удалить запись из набора данных fdRacks, расположенного на этой форме. Для этого у набора данных fdRacks в событии onBeforeDelete, возникающее перед непосредственным удалением записи из набора данных, мы напишем следующий обработчик:
procedure TfrmRacks.fdRacksBeforeDelete(DataSet: TDataSet); begin DeleteCreate(Left,Top,Height,Width,'Удалить стойку?'); if bModal=false then abort; end;
Мы вызываем нашу процедуру DeleteCreate и передадим в нее размеры и координаты нашей формы frmRacks, а также сообщение, которое мы хотим видеть в окне.
При этом, если мы вернемся в процедуру DeleteCreate, то увидим, что в ней мы создавали окно frmDelete. И после создания мы написали, что будем показывать его как ShowModal, то есть, как модальное. Это значит, что код программы в одном текущем потоке остановится, пока мы не закром данное окно.
Следовательно из двух строк нашей процедуры procedure onBeforeDelete будет выполнена только первая. Вторая строка,
if bModal=false then abort;
будет выполнена после закрытия модального окна. А мы вспомним, что когда мы закрываем окно, то мы делаем это одной из двух кнопок, либо «Удалить», либо «Отмена». Когда мы нажимаем н а кнопку «Отмена», то глобальная переменная bModal принимает значение false. Если так, то получается пользователь отказался от удаления (передумал), а значит можно написать команду abort и прекратить удаление.
Результат же команды
DeleteCreate(Left,Top,Height,Width,'Удалить стойку?');
будет такой:

Видите, для чего нужно затемнение? Если его убрать, то наше модальное окно сольется основным. Ну и теперь наше окно конечно же не похоже на стандартное окно приложения, а выполнено в единой стилистике.
Остальные перечисленные окна вызываются точно также, как и frmDelete, только нужно будет вызывать соответствующие процедуры из модуля unit dialogs_.