Delphi - Correctly displaying a Message Dialog in FireMonkey and returning the Modal Result
Asked Answered
P

6

18

I have a VCL application that I am porting to FireMonkey. One of the things that I ran into is that MessageDlg(...) is deprecated in FireMonkey. On digging a bit further, I understand that I have to use the FMX.DialogService.MessageDialog method. So I created a function to display a dialog:

function TfMain.GetDeleteConfirmation(AMessage: String): String;
var
  lResult: String;
begin
  lResult:='';
  TDialogService.PreferredMode:=TDialogService.TPreferredMode.Platform;
  TDialogService.MessageDialog(AMessage, TMsgDlgType.mtConfirmation,
    [ TMsgDlgBtn.mbYes, TMsgDlgBtn.mbCancel ], TMsgDlgBtn.mbCancel, 0,
    procedure(const AResult: TModalResult)
    begin
      case AResult of
        mrYes:    lResult:='Y';
        mrCancel: lResult:='C';
      end;
    end);

  Result:=lResult;
end;

I don't think that I am doing this right as I am not sure I can set a local variable inside an anonymous method, but it compiles nevertheless.

I call it like so:

  if GetDeleteConfirmation('Are you sure you want to delete this entry?')<>'Y' then
    exit;

When I run it, the message dialog shown is this:

enter image description here

It does not show the 2 buttons (Yes, Cancel). Could someone please help me get this right - i.e. correctly show the message dialog with the 2 buttons and send the modal result of the message dialog back as the Result of the function.

I am using Delphi 10.1 Berlin Update 2.

Many thanks in advance!

EDIT 20170320: I corrected my code on the basis of the correct answer by @LURD below and am including it here for completeness:

function TfMain.GetDeleteConfirmation(AMessage: String): String;
var
  lResultStr: String;
begin
  lResultStr:='';
  TDialogService.PreferredMode:=TDialogService.TPreferredMode.Platform;
  TDialogService.MessageDialog(AMessage, TMsgDlgType.mtConfirmation,
    FMX.Dialogs.mbYesNo, TMsgDlgBtn.mbNo, 0,
    procedure(const AResult: TModalResult)
    begin
      case AResult of
        mrYes: lResultStr:='Y';
        mrNo:  lResultStr:='N';
      end;
    end);

  Result:=lResultStr;
end;
Pinkney answered 17/3, 2017 at 8:49 Comment(1)
You can set local variables from within anonymous methods, so that bit is OK. I can't see anything wrong with the rest either.Vintner
B
16

Question:

It does not show the 2 buttons (Yes, Cancel). Could someone please help me get this right - i.e. correctly show the message dialog with the 2 buttons and send the modal result of the message dialog back as the Result of the function.

The Fmx.TDialogService.MessageDialog does not support arbitrary combinations of dialog buttons.

Looking into the source code (Fmx.Dialogs.Win.pas) reveals these valid combinations (mbHelp can be included in all combinations):

  • mbOk
  • mbOk,mbCancel
  • mbYes,mbNo,mbCancel
  • mbYes, mbYesToAll, mbNo, mbNoToAll, mbCancel
  • mbAbort, mbRetry, mbIgnore
  • mbAbort, mbIgnore
  • mbYes, mbNo
  • mbAbort, mbCancel

This means that [mbYes,mbCancel] is not a valid combination, use [mbOk,mbCancel] instead for example.


A final note about the Fmx.TDialogService.MessageDialog. It is normally a synchronous dialog on desktop applications, but asynchronous on mobile platforms. The use case will look a bit different depending on those conditions, so for a multi-platform application, check the value of TDialogService.PreferredMode.

Becker answered 18/3, 2017 at 15:36 Comment(1)
Thank you, @LURD - this did it - I now get the right dialog and also the correct modal result! Just FYI, FMX.Dialogs has the following appropriate choices: mbYesNo, mbYesNoCancel, mbYesAllNoAllCancel, mbOKCancel, mbAbortRetryIgnore, mbAbortIgnore.Pinkney
E
3

Hi friend try this code:

function myMessageDialog(const AMessage: string; const ADialogType: TMsgDlgType;
  const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn): Integer;
var
  mr: TModalResult;
begin
  mr:=mrNone;
  // standart call with callback anonimous method
  TDialogService.MessageDialog(AMessage, ADialogType, AButtons,
    ADefaultButton, 0,
    procedure (const AResult: TModalResult) 
    begin 
      mr:=AResult 
    end);

  while mr = mrNone do // wait for modal result
    Application.ProcessMessages;
  Result:=mr;
end;

Or this:

function MsgBox(const AMessage: string; const ADialogType: TMsgDlgType; const AButtons: TMsgDlgButtons;
    const ADefaultButton: TMsgDlgBtn ): Integer;
var
    myAns: Integer;
    IsDisplayed: Boolean;
begin
    myAns := -1;
    IsDisplayed := False;

While myAns = -1 do
Begin
    if IsDisplayed = False then
    TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton, 0,
            procedure (const AResult: TModalResult)
            begin
                myAns := AResult;
                IsDisplayed := True;
            end);

    IsDisplayed := True;
    Application.ProcessMessages;
End;

Result := myAns;

end;

Enjoy it!

Erleneerlewine answered 17/3, 2017 at 9:42 Comment(9)
Thanks for the code. Your first example seems (conceptually) identical to my code (except for the while loop). I incorporated that into my code, but I still see the message box as outlined in the question. I still do not see the 2 buttons. Just FYI - I am testing this out on Windows 10. Any thoughts?Pinkney
The behavior of TDialogService depends on the PreferredMode. It could be synchronous, asynchronous or depending on platform. Your answer seems to assume async mode, but calling Application.ProcessMessages is an abomination.Becker
@LURD - If I do not specify PreferredMode, does it default to Platform (I assumed it did). Since I am testing this on Windows 10, it should therefore treat the method as Sync - correct?Pinkney
@Rohit, I can't find the default value in the docs, but sync mode for windows and OSX would be a good guess. Otherwise just set it to be sure.Becker
OK - I updated my code to explicitly set it to Platform. And I don't think that the while loop is required (as suggested in this answer). I have updated my code in the question. The issue is still unresolved - I still get the same message dialog as shown in the question and I still cannot get the value of the modal result of the message dialog. Any guidance or help would be appreciated!Pinkney
@Rohit: The whole purpose of the anonymous method parameter is to support asynchronous dialogs. Your code wants to force it to behave like a synchronous dialog, and that requires a loop when the dialog is displayed asynchronously. Not all platforms (most notably, Android) support synchronous dialogs, which is the reason why the parameter had to be added in the first place. You really should redesign your code to use the dialog the way it is intended to be used.Dissyllable
@RemyLebeau, no that is not the case. It is supposed to support both sync and async dialogs, albeit not with the same code. For desktop applications the docs says: On desktop platforms (Windows and OS X), MessageDialog behaves synchronously. The call finishes only when the user closes the dialog box. (Note when PreferredMode is Platform or Sync.)Becker
@RemyLebeau - What LURD says is correct. I stepped thru the code to confirm, and it does make the appropriate MessageDialogSync or MessageDialogAsync call inside the MessageDialog method. In my case, it correctly called MessageDialogSync. So I am not sure why I do not see the buttons on the dialog? Once I get to see the buttons, I can check to see if I can get the right modal value back.Pinkney
I tried using the first sample and it ended up hanging my android app, so I moved to a simpler version of directly calling the function which can also be converted to a reusable function. This avoids the while loop: TDialogService.MessageDialog('Would you like to Exit Icecalc?', TMsgDlgType.mtConfirmation, mbYesNo, TMsgDlgBtn.mbNo, 0, procedure (const AResult: TModalResult) begin if AResult = mrYes then Close; end);Croteau
K
1

On mobile OS's like android there's no such thing as modal dialog.

VCL version is like this: case MessageDlg(.....) of mrOk : DoOkStuff; mrCancel : DoCancelStuff end;

FMX version must be like this: TDialogService.MessageDialog(....., procedure (const AResult: TModalResult) begin case AResult of mrOk : DoOkStuff; mrCancel : DoCancelStuff end; end);

Everything that must be done after closing, should be in this anonymous proc.

Do not try to mimic VCL's MessageDialog and do not use Application.ProcessMessages.

Kittle answered 23/12, 2020 at 23:45 Comment(0)
G
1

Based on other answers, I've added a "Confirm" class function (adapted to return a Boolean) to TApplicationHelper (which also has some extra methods, removed here), currently maintained under the READCOM_App repository

unit Zoomicon.Helpers.FMX.Forms.ApplicationHelper;

interface
uses
  FMX.Forms; //for TApplication

type
  TApplicationHelper = class helper for TApplication
  public
    class function Confirm(Prompt: String): Boolean;
  end;

implementation
  uses
    System.UITypes, //for TMsgDlgType
    FMX.DialogService, //for TDialogService
    FMX.Dialogs; //for mbYesNo

{$region 'TApplicationHelper'}  

//based on https://mcmap.net/q/679231/-delphi-correctly-displaying-a-message-dialog-in-firemonkey-and-returning-the-modal-result
class procedure TApplicationHelper.Confirm(const Prompt: String; const SetConfirmationResult: TProc<Boolean>); //based on https://mcmap.net/q/679231/-delphi-correctly-displaying-a-message-dialog-in-firemonkey-and-returning-the-modal-result
begin
  with TDialogService do
  begin
    PreferredMode := TPreferredMode.Platform;
    MessageDialog(Prompt, TMsgDlgType.mtConfirmation, mbYesNo, TMsgDlgBtn.mbNo, 0,
      procedure(const AResult: TModalResult)
      begin
        //Note: assuming this is executed on the main/UI thread later on, so we just call the "SetResult" callback procedure passing it the dialog result value
        case AResult of
          mrYes: SetConfirmationResult(true);
          mrNo:  SetConfirmationResult(false);
        end;
      end
    );
  end;
end;

{$endregion}

end.

usage example is in the MainForm of READCOM_App

procedure TMainForm.HUDactionNewExecute(Sender: TObject);
begin
  TApplication.Confirm(MSG_CONFIRM_CLEAR_STORY, //confirmation done only at the action level //Note: could also use Application.Confirm since Confirm is defined as a class function in ApplicationHelper (and those can be called on object instances of the respective class too)
  procedure(Confirmed: Boolean)
  begin
    if Confirmed and (not LoadDefaultDocument) then
      NewRootStoryItem;
  end
);

end;

where MSG_CONFIRM_CLEAR_STORY is declared as

resourcestring
  MSG_CONFIRM_CLEAR_STORY = 'Clearing story: are you sure?';
Gelation answered 3/4, 2022 at 18:30 Comment(0)
H
0

This is my answer, perfect function whith Firemonkey delphi Rio 10.3.
The code is inside component button.

procedure TForm3.Button4Click(Sender: TObject);
begin

    MessageDlg('seleccione un boton',System.UITypes.TMsgDlgType.mtConfirmation,
    [System.UITypes.TMsgDlgBtn.mbYes,
    System.UITypes.TMsgDlgBtn.mbNo],0,prozedurale (const AResult:     System.UITypes.TModalResult)
    begin
        if  AResult=mrYES Then
          ShowMessage('tu seleccioneste YES')
        else
           ShowMessage('tu seleccioneste No');

    end);   //Fin de MesageDlg

end;
Howardhowarth answered 25/8, 2021 at 19:14 Comment(0)
G
0

This method works nicely in Alexandria 11.0 application deployed in Android SDK 29.

procedure TForm1.Button1Click(Sender: TObject);
begin

  TDialogService.MessageDialog('Select a button:',
                TMsgDlgType.mtConfirmation,
                FMX.Dialogs.mbYesNoCancel,
                TMsgDlgBtn.mbNo,
                0,
      procedure(const AResult: System.UITypes.TModalResult)
      begin
        if AResult = mrYES Then
          ShowMessage('Yes was selected')
        else if AResult = mrNo Then
          ShowMessage('No was selected')
        else if AResult = mrCancel Then
          ShowMessage('Cancel was selected');

      end); 

end;
Galumph answered 4/2, 2022 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.