Delphi FMX provides support for customized native dialog boxes (like those used for confirmations and warnings), available in all supported target platforms. Understanding how these dialogs work is important not only for everyday development but also during larger initiatives like software modernization or Delphi migration, where UI consistency and cross-platform behavior must be carefully reviewed. This article will show the basics and the rules of how to use these dialogs correctly in your Delphi FMX apps.
What dialogs are available?
There are 3 kinds of native dialogs available by FMX in every OS:
- MessageDialog: a dialog box in a special kind (Error, Confirmation, Information, Warning). It contains text and a custom set of buttons, every button returning a response as an integer to the calling app.
- InputQuery: a dialog box that contains a custom number of text input fields (TEdit controls). This dialog will return an array of strings to the calling app.
- ShowMessage: a simpler dialog box, used only for information with a text and an “OK” button. Nothing is returned to the calling app.
Basics of FMX dialogs
Dialog behavior
According to the platform, dialogs can have two different behaviors:
- Synchronous: the app (that called the dialog) is blocked by the called dialog box until it is closed by the user. This behavior is supported on Windows, MacOS and iOS. But it is not supported on Android (because the OS does not support blocking of the app’s main thread).
- Asynchronous: the app is not blocked by the called dialog box; the app remains accessible for user interactions. This behavior is supported on Windows, macOS, iOS and Android.
The methods for calling Sync dialogs are functions that return responses to the executing app, while the methods for calling Async dialogs are procedures containing anonymous methods (with the dialog responses as parameters) that are executed when the dialog is closed.
Both behaviors are implemented in the FMX platform services.
The actual FMX architecture provides two concepts for using these dialogs, you can choose between:
- Using a platform service from the “FMX.Platform” unit, by declaring an instance of the service interface (as a variable) and the “Supports” routine. Those service interfaces are :
- “IFMXDialogServiceAsync” for Async dialogs and,
- “IFMXDialogServiceSync” for Sync dialogs.
- Or, using the helper class that provides class methods and functions for calling the dialog boxes without the need for an instance of the corresponding dialog box platform service. Those helper classes are :
- “TDialogServiceAsync” (by using the unit “FMX.DialogService.Async”) and,
- “TDialogServiceSync” (by using the unit “FMX.DialogService.Sync”).
Dialog type
For the kind of “MessageDialog”, you have to define the type of your dialog box, which will affect its appearance. Mostly the title of the dialog box (which is localized to the underlying OS language).
The type of the dialog box is set by a value of “TMsgDlgType”:
TMsgDlgType = (mtWarning, mtError, mtInformation, mtConfirmation, mtCustom);
| Value | Meaning | Dialog box title |
| mtWarning | Warns the user about a potential issue. | Warning |
| mtError | Informs the user of an error that occurred. | Error |
| mtInformation | Provides information to the user. | Information |
| mtConfirmation | Ask the user for confirmation. | Confirm |
| mtCustom | None of the above. | Your project name |
Dialog box buttons
Especially for the “MessageDialog”, you can choose a set of buttons (their type is “TMsgDlgBtn”) to appear at the bottom of the MessageDialog. Also, the caption on these buttons is localized according to the OS language.
Each of these buttons, when clicked, will close the dialog box and return a corresponding integer value as “TModalResult”. Because, in fact, the dialog box is a modal form and setting the value of its TModalResult property to a non-zero integer value will close the modal form.
The returned TModalResult value can be used in the caller app, either assigned directly to a variable (in case of Sync dialogs) or in an anonymous method to perform custom actions after closing the dialog box (in case of Async dialogs).
The table below enumerates the MessageDialog buttons and their corresponding returned ModalResult (as named constant and integer):
| TMsgDlgBtn value | Corresponding returned value | ModalResult corresponding integer value |
| mbOK | mrOk | 1 |
| mbCancel | mrCancel | 2 |
| mbAbort | mrAbort | 3 |
| mbRetry | mrRetry | 4 |
| mbIgnore | mrIgnore | 5 |
| mbYes | mrYes | 6 |
| mbNo | mrNo | 7 |
| mbClose | mrClose | 8 |
| mbHelp | mrHelp | 9 |
| mbAll | mrAll | 12 |
| mbNoToAll | mrNoToAll | 13 |
| mbYesToAll | mrYesToAll | 14 |
Also, the “FMX.Dialogs” unit defines constants that you can use to represent some predefined sets of buttons, as shown in the following table:
| mbYesNo | TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo |
| mbYesNoCancel | TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel |
| mbYesAllNoAllCancel | TMsgDlgBtn.mbYes, TMsgDlgBtn.mbYesToAll, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbNoToAll, TMsgDlgBtn.mbCancel |
| mbOKCancel | TMsgDlgBtn.mbOK, TMsgDlgBtn.mbCancel |
| mbAbortRetryIgnore | TMsgDlgBtn.mbAbort, TMsgDlgBtn.mbRetry, TMsgDlgBtn.mbIgnore |
| mbAbortIgnore | TMsgDlgBtn.mbAbort, TMsgDlgBtn.mbIgnore |
Usage
Now, let’s see how to use each kind of these native dialogs.
Using the “ShowMessage”
The simplest kind of dialog you only need to specify the text that will be shown.
The code for calling ShowMessage Async:
| 123456789 | procedure TForm1.Button1Click(Sender: TObject);
var
AsyncService: IFMXDialogServiceAsync;
begin
if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync, IInterface(AsyncService)) then
begin
AsyncService.ShowMessageAsync(‘This is ShowMessage Async’);
end;
end; |
The code for calling ShowMessage Sync:
| 123456789 | procedure TForm1.Button1Click(Sender: TObject);
var
SyncService: IFMXDialogServiceSync;
begin
if TPlatformServices.Current.SupportsPlatformService(IFMXDialogServiceSync, IInterface(SyncService)) then
begin
SyncService.ShowMessageSync(‘This is ShowMessage Sync’);
end;
end; |
Using the “MessageDialog”
As explained in the basics above, for using MessageDialog, you need to specify:
- The message (text of the dialog).
- The dialog type (mtWarning, mtError, mtInformation, mtConfirmation, mtCustom)
- The set of buttons.
- The default button that will have the initial focus when the dialog is shown.
- The help context (0 if no help is used in the app).
For Async MessageDialog, you need, as an additional parameter, an anonymous method to handle the dialog result, like the following code snippet:
| 1234567 | procedure(const AResult: TModalResult)
begin
case AResult of
mrYES:
Label1.Text := 'YES';
end;
end |
But for Sync MessageDialog, the dialog result is returned as an integer from the dialog calling method.
The code for calling MessageDialog Async:
| 12345678910111213141516 | procedure TForm1.Button1Click(Sender: TObject);
var
AsyncService: IFMXDialogServiceAsync;
begin
if TPlatformServices.Current.SupportsPlatformService(IFMXDialogServiceAsync, IInterface(AsyncService)) then
begin
AsyncService.MessageDialogAsync('Question ?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], TMsgDlgBtn.mbNo, 0,
procedure(const AResult: TModalResult)
begin
case AResult of
mrYES:
Label1.Text := 'YES';
end;
end);
end;
end; |
The code for calling MessageDialog Sync:
| 1234567891011 | procedure TForm1.Button1Click(Sender: TObject);
var
SyncService: IFMXDialogServiceSync;
rValue: integer;
begin
if TPlatformServices.Current.SupportsPlatformService(IFMXDialogServiceSync, IInterface(SyncService)) then
begin
rValue := SyncService.MessageDialogSync('Question ?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], TMsgDlgBtn.mbNo, 0);
Label1.Text := rValue.ToString;
end;
end; |
Using the “InputQuery”
For using InputQuery, you need to specify:
- The caption of the dialog box.
- The prompt of each input field (a declared variable of an array of strings), and if you want to mask a field as a password, add ‘#1’ before the prompt (like #1 + ‘Password’).
- The values of each input field: A declared variable of an array of strings, can be filled at runtime (before calling the dialog) to serve as default values.
For Async InputQuery, you need, as an additional parameter, an anonymous method to handle the dialog result (clicked button) and the user input values, like the following code snippet:
| 1234567 | procedure (const AResult: TModalResult; const AValues: array of string)
begin
case AResult of
mrOk:
Label1.Text := AValues[0] + '/' + AValues[1];
end;
end |
But for Sync InputQuery, the dialog calling function returns a boolean value (true if the user clicked OK button). Then the user input values can be accessed through the Values variable.
The code for calling InputQuery Async:
| 123456789101112131415161718192021222324 | procedure TForm1.Button1Click(Sender: TObject);
var
AsyncService: IFMXDialogServiceAsync;
caption, inData: array[0..1] of string;
begin
caption[0] := 'Name :';
caption[1] := #1 + 'Pass :'; // Masking function when #1 attached in front
// Setting initial values
inData[0] := 'admin';
inData[1] := '1234';
if TPlatformServices.Current.SupportsPlatformService(IFMXDialogServiceAsync, IInterface(AsyncService)) then
begin
AsyncService.InputQueryAsync('Input login credentials', caption, inData,
procedure (const AResult: TModalResult; const AValues: array of string)
begin
case AResult of
mrOk:
Label1.Text := AValues[0] + '/' + AValues[1];
end;
end);
end;
end; |
The code for calling InputQuery Sync:
| 12345678910111213141516171819 | procedure TForm1.Button1Click(Sender: TObject);
var
SyncService: IFMXDialogServiceSync;
caption, inData: array[0..1] of string;
begin
caption[0] := 'Name :';
caption[1] := #1 + 'Pass :'; // Masking function when #1 attached in front
// Setting initial values
inData[0] := 'admin';
inData[1] := '1234';
if TPlatformServices.Current.SupportsPlatformService(IFMXDialogServiceSync, IInterface(SyncService)) then
begin
if SyncService.InputQuerySync('Input login credentials', caption, inData)
then
Label1.Text := inData[0] + '/' + inData[1];
end;
end; |
Conclusion
Like any other component in the Delphi frameworks, knowing the mechanism and the basics behind native dialogs allows the developer to use them correctly and effectively in their projects.