Mimicking MessageDlg on mobile platforms

by Jan 7, 2018

This blog post will describe one of the ways to mimic the behaviour of a MessageDlg, or any other modal form for that matter, in Delphi when running on a mobile device.

Mobile devices, or more correctly the OS running on the mobile device won’t allow blocking code, so the traditional Delphi approach of reading a result from a MessageDlg before continuing won’t work.

There different ways of getting the desired behaviour, and this describes one way.

Start a new Firemonkey application, drop a button to call up the dialog, and an editbox, that we can display the user’s choice in (I have chosen the Android Style, the same of course applies to iOS).

Main Form

 

Now create a new form, name it frmDlg and follow these steps:

1.       Drop a TLayout on the form, align to Client, name it layoutDlg

2.       Drop a TRectangle on the Layout, align to Client, name it recBackground

3.       Set the recBackground.Fill.Color to black

4.       Set the recBackground.Opacity to 0.5

5.       Set the recBackground.Sides to false (all of the four sides)

 

Your form now looks very boring
Modal Form
Continue to follow the steps

1.       Drop another TRectangle on the form, name it recFront and set the alignment to VertCenter

2.       Set recFront.Margins.Left and Right to 30

3.       Remove the sides, as you did with recBack

4.       Set recFront.XRadius and YRadius to 6

Your dialog mimic is starting to take shape, and should look something like
Note that both rectangles have the Layout as parent.
StructureV1
Moving on, there are a few more steps.

1.       On recFront drop a TShadowEffect, shadowDlg

2.       Set the ShadowDlg.Distance to 5

3.       Set the ShadowDlg.Softness to 0.2

Make sure the shadow is on the recFront (recFront is parent)
StructureV2
Let’s make the dialog rectangle just a little better

1.       Set the recFront.Fill.Kind to Gradient

2.       Double-Click the recFront.Fill.Gradient property to get the property editor running

 dd  BrushDesigner

Cl   click the left dot of the two dots at the bottom of the disgner

        Brush

          Set its RGB values as shown in the picture

2.       Click the right one and set its to 255 (all of them), this is white

Now we just need a few components on the recFront and the GUI is done

3.       Drop a TLabel named lblMessage on the recFront, recFront is parent

4.       lblMessage.Align should be Top

5.       lblMessage.Margins.Top is 30, left and right are 10

6.       lblMessage.TextSettings.HorzAlign is Center

7.       lblMessage.TextSettings.WordWrap should be true

8.       lblMessage.Height is 60

9.       Drop a TLayout on recFront, name it layoutBottom (recFront is parent)

10.   layoutBottom.align is set to Bottom

11.   On layoutBottom drop two buttons, named btnOK and btnCancel

12.   btnOK.align is left, margins.left set to 5, and Text is ‘OK’

13.   btnCancel.align is right, margins.right set to 5, and Text is ‘Cancel’

      You should now have a form looking similar to my form below

      Dialog

     With the structure pane reflecting the parenting of the components

       Struct3

      Now we need to implement some code on this dialog. The object is to

1.       Display the dialog

2.       Having a text in the label

3.       And detect if the user clicked Ok or Cancel

To do so, I want the calling code to be as simple as possible, now we cannot have blocking code, so we cannot have a function waiting to return the user choice back to the main form. Let’s start with just displaying the form, adding the other bullets later.
Remember you put everything on layoutDlg, well there was a reason for that. It is possible to move a layout from one form to another, so that’s what we are going to do in the procedure that starts the dialog.
Procedure TfrmDlg.ShowDialog(aParent : TFMXObject; aText : string);
begin
  //First we set the text in the label
  lblMessage.Text := aText;
  //Then we move the entire Layout to the form (well, any FMXObject)
  layoutDlg.Parent := aParent;
  //The following lines shouldnt be necessary
  //I have however had one occasion where they were
  //I havent been able to reproduce, but they wont hurt
  recBackground.BringToFront;
  recFront.BringToFront;
end
 So we create a procedure on the form with the dialog as I did above, remember to make it a public procedure.
To call this procedure from the main form, just use the dlgForm, and call it on the buttons OnClick eventhandler
procedure TForm1.Button1Click(Sender: TObject);
begin
   frmDlg.ShowDialog(self, Press ok or cancel
end
And this will bring our dialog up very nicely, with the text in the label. Now we need to react to the two buttons OnClick Events, but we of course need to do this from the main form.
To do so, there are different ways. We need to have a couple of events on the dialog. In the private section of the “modal” form define two events of type TNotifyEvent

 

private
    FOnOkBtnClick: TNotifyEvent;
    FOnCancelBtnClick: TNotifyEvent;
Expand your procedure to

 

Procedure TfrmDlg.ShowDialog(aParent : TFMXObject; aText : string; aOKClick, aCancelClick : TNotifyEvent);
//Procedure TfrmDlg.ShowDialog(aParent : TFMXObject; aText : string);
begin
  //First we set the text in the label
  lblMessage.Text := aText;
  //Then we move the entire Layout to the form (well, any FMXObject)
  layoutDlg.Parent := aParent;
  //The following lines shouldnt be necessary
  //I have however had one occasion where they were
  //I havent been able to reproduce, but they wont hurt
  recBackground.BringToFront;
  recFront.BringToFront;
  self.FOnOkBtnClick := aOKClick;
  self.FOnCancelBtnClick := aCancelClick;
end

 

And on the OK, and cancel buttons OnClick Eventhandlers write the following code

 

procedure TfrmDlg.btnCancelClick(Sender: TObject);
begin
  //Set the parent back to self
  self.layoutDlg.Parent := self;
  //If the event is assigned, call it
  if assigned(self.FOnCancelBtnClick) then
    self.FOnCancelBtnClick(self);
end
 
procedure TfrmDlg.btnOKClick(Sender: TObject);
begin
  //Set the parent back to self
  self.layoutDlg.Parent := self;
  //If the event is assigned, call it
  if assigned(self.FOnOkBtnClick) then
    self.FOnOkBtnClick(self);
end

 

You could also have public properties for those events, in which case your public section of the dialog form would appear as

 

public
    { Public declarations }
    Procedure ShowDialog(aParent : TFMXObject; aText : string; aOKClick, aCancelClick : TNotifyEvent);
    property OnOkBtnClick : TNotifyEvent read FOnOkBtnClick write SetOnOkBtnClick;
    property OnCancelBtnClick : TNotifyEvent read FOnCancelBtnClick write SetOnCancelBtnClick;

 

Now, to call the dialog from your main form, you pass in two procedures with the signature matching a TNotifyEvent: Procedure Form1.procedureName(sender : TObject); I have defined in my example these two

 

procedure TForm1.DlgCancel(sender : TObject);
begin
  Edit1.Text := Cancel was clicked
end
 
procedure TForm1.DlgOK(sender : TObject);
begin
  Edit1.Text := Ok was clicked
end

 

Now, when you on the main forms buttons OnClick eventhandler have this

 

procedure TForm1.Button1Click(Sender: TObject);
begin
   frmDlg.ShowDialog(self, Press ok or cancel, dlgOK, dlgCancel);
end

 

 

Your dialog appears nicely

MainDialog