In Firemonkey, message dialogs have changed in Delphi 10.1 Berlin, and MessageDlg
has been deprecated in favor to use the new dialog services. However, in any case, I would like to bypass any system dialogs (at least for messages) and use my own synchronous in-form dialog instead.
I managed to write a single form to accomplish this, and it works. However, it's extremely sloppy, specifically the method of how it waits. I don't want to use a callback procedure, so I want my own version of MessageDlg
to instead wait for a response from the user, just like regular modal dialogs. (Actually, I call mine MsgPrompt
.)
In particular, I need to do something else at this spot:
while not F.FDone do begin
Application.ProcessMessages;
Sleep(50);
end;
... for obvious reasons.
One example of why I don't want (and can't use) a callback procedure, is because I need to use it in the main form's OnCloseQuery
, and prompt the user if they're sure they want to close. It would be impossible to make that work, because the OnCloseQuery
event handler would exit before the user made a choice
How should I appropriately wait for this input synchronously (mimicking a modal dialog) without blocking the main UI thread and hindering its responsiveness?
Custom dialog unit - please refer to where I say HORRIBLE, HORRIBLE DESIGN
:
unit uDialog;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, FMX.Layouts, System.ImageList, FMX.ImgList;
type
TDialogForm = class(TForm)
DialogLayout: TLayout;
DimPanel: TPanel;
DialogPanel: TPanel;
ButtonPanel: TPanel;
btnYes: TButton;
btnNo: TButton;
btnOK: TButton;
btnCancel: TButton;
btnAbort: TButton;
btnRetry: TButton;
btnIgnore: TButton;
btnAll: TButton;
btnNoToAll: TButton;
btnYesToAll: TButton;
btnHelp: TButton;
btnClose: TButton;
DialogLabel: TLabel;
imgError: TImageControl;
imgInfo: TImageControl;
imgConfirm: TImageControl;
imgWarn: TImageControl;
procedure FormCreate(Sender: TObject);
procedure DialogButtonClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FCloseDialogProc: TInputCloseDialogProc;
FDone: Boolean;
procedure ShowButtons(const AButtons: TMsgDlgButtons);
procedure ShowIcon(const ADialogType: TMsgDlgType);
procedure SetDefaultButton(const ABtn: TMsgDlgBtn);
public
end;
var
DialogForm: TDialogForm;
procedure SetDialogDefaultParent(AValue: TFmxObject);
function MsgPrompt(const AMessage: string;
const ADialogType: TMsgDlgType; const AButtons: TMsgDlgButtons;
const ADefaultButton: TMsgDlgBtn): TModalResult;
procedure MessageDlg(const AMessage: string;
const ADialogType: TMsgDlgType; const AButtons: TMsgDlgButtons;
const ADefaultButton: TMsgDlgBtn; const ACloseDialogProc: TInputCloseDialogProc);
implementation
{$R *.fmx}
var
_DefaultParent: TFmxObject;
procedure SetDialogDefaultParent(AValue: TFmxObject);
begin
_DefaultParent:= AValue;
end;
function MsgPrompt(const AMessage: string;
const ADialogType: TMsgDlgType; const AButtons: TMsgDlgButtons;
const ADefaultButton: TMsgDlgBtn): TModalResult;
var
R: TModalResult;
begin
MessageDlg(AMessage,
ADialogType,
AButtons,
ADefaultButton,
procedure(const AResult: TModalResult)
begin
R:= AResult;
end);
Result:= R;
end;
procedure MessageDlg(const AMessage: string;
const ADialogType: TMsgDlgType; const AButtons: TMsgDlgButtons;
const ADefaultButton: TMsgDlgBtn; const ACloseDialogProc: TInputCloseDialogProc);
var
F: TDialogForm;
begin
F:= TDialogForm.Create(nil);
try
//TODO: Move these assignments into the form itself, perhaps its constructor.
F.FCloseDialogProc:= ACloseDialogProc;
F.DialogLabel.Text:= AMessage;
F.ShowButtons(AButtons);
F.ShowIcon(ADialogType);
F.DialogLayout.Parent:= _DefaultParent;
F.SetDefaultButton(ADefaultButton);
//TODO: Use another method!!!!!!!
while not F.FDone do begin // <---- HORRIBLE, HORRIBLE DESIGN.
Application.ProcessMessages;
Sleep(50);
end;
finally
F.Close;
end;
end;
{ TDialogForm }
procedure TDialogForm.FormCreate(Sender: TObject);
begin
DialogLayout.Align:= TAlignLayout.Client;
DimPanel.Align:= TAlignLayout.Client;
DialogLabel.Text:= '';
end;
procedure TDialogForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:= TCloseAction.caFree;
end;
procedure TDialogForm.DialogButtonClick(Sender: TObject);
var
B: TButton;
R: TModalResult;
begin
DialogLayout.Visible:= False;
B:= TButton(Sender);
case B.Tag of
0: R:= mrYes;
1: R:= mrNo;
2: R:= mrOK;
3: R:= mrCancel;
4: R:= mrAbort;
5: R:= mrRetry;
6: R:= mrIgnore;
7: R:= mrAll;
8: R:= mrNoToAll;
9: R:= mrYesToAll;
10: R:= mrHelp;
11: R:= mrClose;
else R:= mrOK;
end;
FCloseDialogProc(R);
FDone:= True;
end;
procedure TDialogForm.ShowIcon(const ADialogType: TMsgDlgType);
begin
case ADialogType of
TMsgDlgType.mtWarning: imgWarn.Visible:= True;
TMsgDlgType.mtError: imgError.Visible:= True;
TMsgDlgType.mtInformation: imgInfo.Visible:= True;
TMsgDlgType.mtConfirmation: imgConfirm.Visible:= True;
TMsgDlgType.mtCustom: ; //TODO
end;
end;
procedure TDialogForm.SetDefaultButton(const ABtn: TMsgDlgBtn);
var
B: TButton;
begin
B:= nil;
case ABtn of
TMsgDlgBtn.mbYes: B:= btnYes;
TMsgDlgBtn.mbNo: B:= btnNo;
TMsgDlgBtn.mbOK: B:= btnOK;
TMsgDlgBtn.mbCancel: B:= btnCancel;
TMsgDlgBtn.mbAbort: B:= btnAbort;
TMsgDlgBtn.mbRetry: B:= btnRetry;
TMsgDlgBtn.mbIgnore: B:= btnIgnore;
TMsgDlgBtn.mbAll: B:= btnAll;
TMsgDlgBtn.mbNoToAll: B:= btnNoToAll;
TMsgDlgBtn.mbYesToAll: B:= btnYesToAll;
TMsgDlgBtn.mbHelp: B:= btnHelp;
TMsgDlgBtn.mbClose: B:= btnClose;
end;
if Assigned(B) then
if B.Visible then
if B.CanFocus then
B.SetFocus;
end;
procedure TDialogForm.ShowButtons(const AButtons: TMsgDlgButtons);
begin
if TMsgDlgBtn.mbYes in AButtons then begin
btnYes.Visible:= True;
end;
if TMsgDlgBtn.mbNo in AButtons then begin
btnNo.Visible:= True;
end;
if TMsgDlgBtn.mbOK in AButtons then begin
btnOK.Visible:= True;
end;
if TMsgDlgBtn.mbCancel in AButtons then begin
btnCancel.Visible:= True;
end;
if TMsgDlgBtn.mbAbort in AButtons then begin
btnAbort.Visible:= True;
end;
if TMsgDlgBtn.mbRetry in AButtons then begin
btnRetry.Visible:= True;
end;
if TMsgDlgBtn.mbIgnore in AButtons then begin
btnIgnore.Visible:= True;
end;
if TMsgDlgBtn.mbAll in AButtons then begin
btnAll.Visible:= True;
end;
if TMsgDlgBtn.mbNoToAll in AButtons then begin
btnNoToAll.Visible:= True;
end;
if TMsgDlgBtn.mbYesToAll in AButtons then begin
btnYesToAll.Visible:= True;
end;
if TMsgDlgBtn.mbHelp in AButtons then begin
btnHelp.Visible:= True;
end;
if TMsgDlgBtn.mbClose in AButtons then begin
btnClose.Visible:= True;
end;
end;
end.
Custom dialog FMX (NOTE: Image data is removed to spare space):
object DialogForm: TDialogForm
Left = 0
Top = 0
Caption = 'Form2'
ClientHeight = 574
ClientWidth = 503
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
OnCreate = FormCreate
OnClose = FormClose
DesignerMasterStyle = 0
object DialogLayout: TLayout
Align = Top
Size.Width = 503.000000000000000000
Size.Height = 529.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
object DimPanel: TPanel
Align = Top
Opacity = 0.860000014305114800
Size.Width = 503.000000000000000000
Size.Height = 489.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
object DialogPanel: TPanel
Anchors = [akLeft, akTop, akRight, akBottom]
Position.X = 40.000000000000000000
Position.Y = 40.000000000000000000
Size.Width = 425.000000000000000000
Size.Height = 401.000000000000000000
Size.PlatformDefault = False
StyleLookup = 'DialogPanelStyle1'
TabOrder = 0
object ButtonPanel: TPanel
Align = Bottom
Margins.Left = 3.000000000000000000
Margins.Top = 3.000000000000000000
Margins.Right = 3.000000000000000000
Margins.Bottom = 3.000000000000000000
Position.X = 3.000000000000000000
Position.Y = 355.000000000000000000
Size.Width = 419.000000000000000000
Size.Height = 43.000000000000000000
Size.PlatformDefault = False
StyleLookup = 'Panel2Style1'
TabOrder = 0
object btnYes: TButton
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = 62.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
Text = 'Yes'
Visible = False
OnClick = DialogButtonClick
end
object btnNo: TButton
Tag = 1
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = -274.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
Text = 'No'
Visible = False
OnClick = DialogButtonClick
end
object btnOK: TButton
Tag = 2
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = 241.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
Text = 'OK'
Visible = False
OnClick = DialogButtonClick
end
object btnCancel: TButton
Tag = 3
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = -610.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 3
Text = 'Cancel'
Visible = False
OnClick = DialogButtonClick
end
object btnAbort: TButton
Tag = 4
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = -778.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 4
Text = 'Abort'
Visible = False
OnClick = DialogButtonClick
end
object btnRetry: TButton
Tag = 5
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = 62.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 5
Text = 'Retry'
Visible = False
OnClick = DialogButtonClick
end
object btnIgnore: TButton
Tag = 6
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = 241.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 6
Text = 'Ignore'
Visible = False
OnClick = DialogButtonClick
end
object btnAll: TButton
Tag = 7
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = -694.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 7
Text = 'All'
Visible = False
OnClick = DialogButtonClick
end
object btnNoToAll: TButton
Tag = 8
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = -22.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 8
Text = 'No to All'
Visible = False
OnClick = DialogButtonClick
end
object btnYesToAll: TButton
Tag = 9
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = 241.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 9
Text = 'Yes to All'
Visible = False
OnClick = DialogButtonClick
end
object btnHelp: TButton
Tag = 10
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = -358.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 10
Text = 'Help'
Visible = False
OnClick = DialogButtonClick
end
object btnClose: TButton
Tag = 11
Align = Right
Cursor = crHandPoint
Margins.Left = 2.000000000000000000
Margins.Top = 2.000000000000000000
Margins.Right = 2.000000000000000000
Margins.Bottom = 2.000000000000000000
Position.X = -526.000000000000000000
Position.Y = 2.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 47.000000000000000000
Size.PlatformDefault = False
TabOrder = 11
Text = 'Close'
Visible = False
OnClick = DialogButtonClick
end
end
object DialogLabel: TLabel
Align = Client
StyledSettings = [Family, Style, FontColor]
Margins.Left = 5.000000000000000000
Margins.Top = 5.000000000000000000
Margins.Right = 5.000000000000000000
Margins.Bottom = 5.000000000000000000
Size.Width = 415.000000000000000000
Size.Height = 342.000000000000000000
Size.PlatformDefault = False
TextSettings.Font.Size = 18.000000000000000000
TextSettings.HorzAlign = Center
Text = 'DialogLabel'
end
object imgError: TImageControl
Align = Top
Bitmap.PNG = {}
Margins.Left = 5.000000000000000000
Margins.Top = 5.000000000000000000
Margins.Right = 5.000000000000000000
Margins.Bottom = 5.000000000000000000
Size.Width = 303.000000000000000000
Size.Height = 120.000000000000000000
Size.PlatformDefault = False
TabOrder = 4
Visible = False
end
object imgInfo: TImageControl
Align = Top
Bitmap.PNG = {}
Margins.Left = 5.000000000000000000
Margins.Top = 5.000000000000000000
Margins.Right = 5.000000000000000000
Margins.Bottom = 5.000000000000000000
Position.Y = 49.000000000000000000
Size.Width = 303.000000000000000000
Size.Height = 120.000000000000000000
Size.PlatformDefault = False
TabOrder = 3
Visible = False
end
object imgConfirm: TImageControl
Align = Top
Bitmap.PNG = {}
Margins.Left = 5.000000000000000000
Margins.Top = 5.000000000000000000
Margins.Right = 5.000000000000000000
Margins.Bottom = 5.000000000000000000
Position.Y = 98.000000000000000000
Size.Width = 303.000000000000000000
Size.Height = 120.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
Visible = False
end
object imgWarn: TImageControl
Align = Top
Bitmap.PNG = {}
Margins.Left = 5.000000000000000000
Margins.Top = 5.000000000000000000
Margins.Right = 5.000000000000000000
Margins.Bottom = 5.000000000000000000
Position.Y = 147.000000000000000000
Size.Width = 303.000000000000000000
Size.Height = 120.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
Visible = False
end
end
end
end
end
In the main form's OnCreate
event handler, to instruct where to embed these dialogs:
SetDialogDefaultParent(Self);
Usage:
case MsgPrompt('This is a sample message.', TMsgDlgType.mtInformation,
[TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], TMsgDlgBtn.mbNo) of
mrYes: begin
//
end;
else begin
//
end;
end;
OnCloseQuery
, and prompt the user if they're sure they want to close. It would be impossible to make that work, because theOnCloseQuery
event handler would exit before the user made a choice. – Jerry Dodge