I have the following Delphi code for showing a modal message on Android which worked fine on 10.1 Berlin, but stopped working on Delphi 10.2.1 Tokyo. This procedure now hangs the Android app.
procedure customShowMessage(AMessage: string);
//good idea to have our own procedure that we can tweak, as even for VCL and windows, we have done show message differently over the years due to all sorts of funny problems
var
LModalWindowOpen: boolean;
begin
LModalWindowOpen := true;
TDialogService.MessageDialog(AMessage, TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0,
procedure(const AResult: TModalResult)
begin
LModalWindowOpen := false;
end);
while LModalWindowOpen do
begin
Application.ProcessMessages; //since 10.2 Tokyo, popup never shows and this loops forever
end;
end;
I suspect it possibly has something to do with the change in Tokyo as to how the app runs in the main thread. Not sure what I can replace Application.ProcessMessages with that will let the dialog show, so that the user can click on something.
I have a lot of places this is used, so changing it to work using a callback is going to be a lot of work, and restructuring.
On Android we have only asynchronous dialog boxes. If we want they act as modal dialog box, we have to do ourself.
The solution with a ProcessMessage loop is an idea, but I don't think it's the best approach.
An other one is to add a transparent (or opaque) layout (or rectangle) on your form before displaying the dialog box and when you have an answer, you can remove the blockin layout.
You can also use TFrameStand from Andrea Magni (downloadable directly from GetIt) who propose to use a TFrame as a dialog box.
https://github.com/andrea-magni/TFrameStand
I have restructured a lot of code to use asynchronous callbacks, but where callback hell ensues (especially on large existing projects), I have found the following works:
Instead of just using Application.ProcessMessages, I am now calling CheckSynchronize as well.
procedure TfrmStock.WaitForModalWindowToClose;
begin
while FModalWindowOpen do
begin
Sleep(40);
Application.ProcessMessages;
CheckSynchronize;
end;
end;
I think the following code should work:
function ShowMessageOKCancel(AMessage: String): String;
var
lResultStr: String;
begin
lResultStr:='';
TDialogService.PreferredMode:=TDialogService.TPreferredMode.Platform;
TDialogService.MessageDialog(AMessage, TMsgDlgType.mtConfirmation,
FMX.Dialogs.mbOKCancel, TMsgDlgBtn.mbOK, 0,
procedure(const AResult: TModalResult)
begin
case AResult of
mrOK: lResultStr:='O';
mrCancel: lResultStr:='C';
end;
end);
Result:=lResultStr;
end;
When you call this function, it should show a dialog with your message and the two buttons OK and Cancel. The return value will indicate which button was clicked.
Related
I'm programming apps in Android with Delphi 10 Seattle and I found a problem...
I need to show a form and in this form I'll use a variable and set a value in it but after the form.show I'll continue using this variable to calculating it and get the value that I need.
The problem is that when the form shows the function continue before the user typing the value and press the confirm button on form and it's causing a problem with the results.
I already tried to use a inputbox but its cause the same problem.... It's interesting because in Delphi XE6 I had the same code and its work perfectly.
When I tried to search a solution I found a non-blocking that was implemented in Delphi XE7 forward.
Here a dumb example to be easily to understand what I wanted to do:
procedure TfrmForm.Button1Click(Sender: TObject);
var
vExample, vResult: Integer
begin
frmForm1.Edit1.Text := '';
frmForm1.Show;
if Length(form.Edit1.Text) = 0 then
vExample := 0
else
vExample := StrToInt(form.Edit1.Text);
vResult := vExample * 5;
ShowMessage('The result is ' + IntToStr(vResult));
end;
The problem is that always show 0 in the message and I don't know how to solve this problem...
When I open my form for the first time I get no violation, but when I first select a TEdit field and then close the form and then recreate the form and open it I get the Violation.
Code for creating the form:
procedure TfrmNocoreDKS.actConfigExecute(Sender: TObject);
var
confForm: TConfiguratie;
begin
confForm := TConfiguratie.Create(nil);
confForm.ShowModal(
procedure(ModalResult: TModalResult)
begin
confForm.DisposeOf;//Also tried confForm.Free;
end);
end;
I've also tried this for creating the form:
procedure TfrmNocoreDKS.actConfigExecute(Sender: TObject);
var
confForm: TConfiguratie;
begin
confForm := TConfiguratie.Create(nil);
try
confForm.ShowModal(
procedure(ModalResult: TModalResult)
begin
end);
finally
confForm.free;
end;
end;
Code for Closing the form:
procedure TConfiguratie.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.caFree;
end;
Because the violation only appears when you click on any TEdit and then close the form I think it has something to do with the virtual keyboard, but i'm not sure. I don't have any methods that use the virtual keyboard itself.
Update
While my suggestions here are as documented, there are still problems with Android and multiple forms. See later in this post.
Do not call DisposeOf() or Free at all. The FormClose() and the caFree call is the key to make it work.
The documentation how to dispose of a modal dialog has been changed: Using FireMonkey Modal Dialog Boxes.
The FireMonkey architects has struggled with this for several versions now, and finally it works.
Example from doc how to create a modal dialog:
procedure MyCurrentForm.MyButtonClick(Sender: TObject);
var
dlg: TMyModalForm;
begin
// Create an instance of a form.
dlg := TMyModalForm.Create(nil);
// Configure the form. For example, give it a display name.
dlg.Caption := 'My Modal Dialog Box';
// Show your dialog box and provide an anonymous method that handles the closing of your dialog box.
dlg.ShowModal(
procedure(ModalResult: TModalResult)
begin
// Do something.
end
);
end;
And to free your modal dialog:
procedure TMyModalForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.caFree;
end;
Update
The OP has tried this solution and it does not work as expected.
Looking into QC, there are reports claiming that this does not work as expected on mobile android platforms:
RSP-9692 Runtime creation of forms in Android
and
RSP-9665 Access Violation in FMX.Platform.Android SendCMGestureMessage.
(You must login to access them).
The latter explains what is happening. When the modal form is destroyed, it is possible that FFocusedControl points to a destroyed control. When ARC is trying to release FFocusedControl this will cause a segmentation fault. FFocusedControl must be declared [weak]. See the RSP-9665 for more details.
There is also QC-126524 [Android] Open/Close/Free sub form multiple times may cause crash on Android Platform when removing Focus from TEdit reporting the same thing and closed as resolved in XE7. This apparently not true.
Embarcadero documentation regarding FMX ShowModal and mobile platforms says
Caution: Modal dialog boxes are not supported in Android apps. Instead of calling ShowModal, you should call Show, and have the form return and call your event. We recommend that you not use modal dialogs on either of the mobile platforms (iOS and Android) because unexpected behavior can result. Not using modal dialogs eliminates potential problems in debugging and supporting your mobile apps.
This issue seemed to be only accuring in Delphi XE7. I am now using Delphi XE8 and don't have this problem anymore.
Normally without having to code the android back button functions and lets you got back to the previous form. In my app i'm working on instead of going back it shuts down the application. I also tried handling the back button by code but this also didn't work, it ignored the code!
Here is the code I used to handle the backbutton:
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;
var KeyChar: Char; Shift: TShiftState);
var
FService: IFMXVirtualKeyboardService;
begin
if Key = vkHardwareBack then
begin
TPlatformServices.Current.SupportsPlatformService
(IFMXVirtualKeyboardService, IInterface(FService));
if (FService <> nil) and (TVirtualKeyboardState.Visible
in FService.VirtualKeyBoardState) then
begin
// Back button pressed, keyboard visible, so do nothing...
end
else
begin
// Back button pressed, keyboard not visible or not supported on this platform
close;
end;
end;
end;
I used to use Delphi XE5 and now I use XE6 and hoped the problem was solved but regrettingly not. Also the above code is for the Delphi XE6 version, for XE5 it is slightly different.
UPDATE:
I've found a fix for my problem. But my delphi still reacts really strange. When I create a new project and add the files from my previous project i'm able to go back with the android backbutton. But as soon as I save the project again and then try to run it then it stops working.
The back button has a special function in the android framework- onBackPressed(). I don't know if it was ported to Delphi, but I assume so. It won't come through that API
After creating a new project in a new directory and then adding all the files too the project the issue had dissapeared! The problem seemed to be somewhere in de dproj file. I tried too find it, but it was to big for me to locate it.
You need to trap the "Back" keypress in your FormKeyUp procedure so that it isn't passed to the operating system after you act on it:
if (Key = vkHardwareBack) then
begin
Key := 0;
{ Do something else }
end;
I am having the dandiest time trying to figure out why my modal form will not close!
Using Delphi XE-5 and FireMonkey Mobile App (Android), i followed the the info "ShowModal Dialogs in FireMonkey Mobile Apps"
for demo purposes, i created a new Firemonkey Mobile delphi application and added a secondary firemonkey mobile form. From the main form, i use the code from the article:
procedure TForm1.Button1Click(Sender: TObject);
var
Form2: TForm2;
begin
Form2 := TForm2.Create(nil);
Form2.ShowModal(procedure(ModalResult: TModalResult)
begin
if ModalResult = mrOK then
begin
//
end;
Form2.DisposeOf;
end);
end;
On the secondary form, i assign the "Ok" and "Cancel" buttons modalresult property to "mrCancel" and "mrOK", respectively. However, when the modal dialog is shown, neither button makes the dialog close. I even tried adding onClick events and assigning the modalresult by code. Why wont the form close? I guess I need assurance that I did everthing right and possible its my PHONE (device)?
In order to close your modal dialog, use this pattern:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.caFree;
end;
and remove your call Form2.DisposeOf;, since the ModalResult setter needs to operate on a valid object.
The documentation has been updated in XE7, see Using FireMonkey Modal Dialog Boxes.
See also ShowModal on Android for the details why DisposeOf is wrong.
In Delphi for windows there is no problem to Free (Form.free) closed secondary dynamicaly-created form because where is "ShowModal" method. But Delphi for Android does not support Form.ShowModal, and we have to use Show method. But I figured out what when I close (Form.close) secondary form it is still in memory and even run code Onresize event (???). What is the best way to Free forms in non-Modal call?
In another words: How do I close a form from an OnClick event handler on that form, and ensure that the form's destructor runs?
Update
See important note below.
In XE5 for Android there is a possibility to show a form with modal results, an overloaded ShowModal procedure using an anonymous method:
procedure ShowModal(const ResultProc: TProc); overload;
You can find it described in this article by Marco Cantu, Delphi XE5 Anonymous ShowModal and Android.
Here is the example how to use this procedure:
var
dlg: TForm1;
begin
dlg := TForm1.Create(nil);
// select current value, if avaialble in the list
dlg.ListBox1.ItemIndex := dlg.ListBox1.Items.IndexOf(Edit1.Text);
dlg.ShowModal(
procedure(ModalResult: TModalResult)
begin
if ModalResult = mrOK then
// if OK was pressed and an item is selected, pick it
if dlg.ListBox1.ItemIndex >= 0 then
edit1.Text := dlg.ListBox1.Items [dlg.ListBox1.ItemIndex];
dlg.DisposeOf; // Wrong !!!, see note below
end);
Note that the dlg.DisposeOf; will force the form to be destroyed, overriding the ARC automatic handling.
You can also find a description in the documentation, Using Modal Dialog Boxes in Mobile Apps and here, ShowModal Dialogs in FireMonkey Mobile Apps.
As found by others, http://www.malcolmgroves.com/blog/?p=1585, calling DisposeOf inside the anonymous method is wrong because the anonymous frame must be able to handle ModalResult from a valid object. Use this pattern instead to free the modal dialog, Freeing Your Modal Dialog Box.
procedure TModalForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.caFree;
end;
Don't forget to set in the ObjectInspector
ModalResult = mrOK
or in your
procedure TForm1.ExitButtonClick(Sender: TObject);
begin
ModalResult := mrOK;
end;
for the example dlg.ShowModal above!