1、直接调用 ShowModal 肯定是不行的,Android 下直接抛出异常。而 iOS、OSX、Windows 下是没问题的。
2、像下面这样用循环模拟 ShowModal 也是不行的,如果只是这么简单,Delphi 早就实现了。这个代码在我手机上实测存在的主要问题就是你按回退键没响应。
var F:TForm2; begin F:=TForm2.Create(nil); F.Show; while F.Visible and (F.ModalResult=mrNone) do begin Application.ProcessMessages; Sleep(10); end; FreeAndNil(F); end;
3、像下面的用法也是错误的:
procedure TForm1.Button1Click(Sender:TObject) var dlg: TForm2; begin dlg := TForm2.Create(nil); dlg.ShowModal( procedure(ModalResult: TModalResult) begin if ModalResult = mrOK then if dlg.ListBox1.ItemIndex >= 0 then edit1.Text := dlg.ListBox1.Items [dlg.ListBox1.ItemIndex]; dlg.DisposeOf; end); end;
这个的问题在于 dlg 是局部变量,ShowModal 回调的匿名函数里访问Button1Click 里的局部变量是不安全的(栈可能已经错乱)。这块 FMX 的设计真是一个败笔,应该加入实例的地址。当然了,如果 dlg 是一个全局变量,上面的代码就不存啥问题了。
正确的用法:
好吧,得罪了人,批判了别人的不对,总得给出一个对的方法吧。这个方法实际上也说不上真正的对,我暂时称之为对是因为这是目前我能想到的相对完美的解决方案。
type TFormModalProc = reference to procedure(F: TForm); TFormModalHook = class(TComponent) private FForm: TForm; FCloseAction: TCloseAction; FOldClose: TCloseEvent; FResultProc: TFormModalProc; procedure DoFormClose(Sender: TObject; var Action: TCloseAction); public constructor Create(AOwner: TComponent); override; procedure ShowModal(AResult: TFormModalProc); end; procedure ModalDialog(F: TForm; OnResult: TFormModalProc; ACloseAction: TCloseAction = TCloseAction.caFree); var AHook: TFormModalHook; begin AHook := TFormModalHook.Create(F); AHook.FCloseAction := ACloseAction; AHook.ShowModal(OnResult); end; { TFormModalHook } constructor TFormModalHook.Create(AOwner: TComponent); begin inherited Create(AOwner); FForm := AOwner as TForm; FOldClose := FForm.OnClose; FForm.OnClose := DoFormClose; FCloseAction := TCloseAction.caFree; end; procedure TFormModalHook.DoFormClose(Sender: TObject; var Action: TCloseAction); begin if FForm.ModalResult = mrNone then FForm.ModalResult := mrCancel; Action := FCloseAction; if Assigned(FOldClose) then FOldClose(Sender, Action); end; procedure TFormModalHook.ShowModal(AResult: TFormModalProc); begin FResultProc := AResult; {$IFDEF ANDROID} FForm.ShowModal( procedure(AResult: TModalResult) var AHook: TFormModalHook; AForm: TForm; I: Integer; AChild: TComponent; begin if Screen.ActiveForm is TForm then AForm := Screen.ActiveForm as TForm else begin raise Exception.Create('You should not in here.'); end; if Assigned(AForm) then begin AForm.OnClose := FOldClose; for I := 0 to AForm.ComponentCount - 1 do begin AChild := FForm.Components[I]; if AChild is TFormModalHook then begin (AChild as TFormModalHook).FResultProc(AForm); FreeAndNil(AChild); Break; end; end; end; end); {$ELSE} FForm.ShowModal; FResultProc(FForm); {$ENDIF} end;
好吧,代码看起来有点多,多就多吧。用法很简单,用它替换 TForm.ShowModal 方法,如:
procedure TForm1.Button1Click(Sender: TObject); var F: TForm2; begin F := TForm2.Create(nil); ModalDialog(F, procedure(AForm: TForm) begin ShowMessage(AForm.Name + ' Modal result ready'); end); end;
【注意】
不要在 ModalDialog 的回调函数中,在可能引发消息循环处理的地方,如:ShowMessage/MessageDlg/ProcessMessages 等函数的后面再引用 AForm 的地址,因为在 FMX 框架下,它很可能会被释放掉了。
完整的单元请加官方群(250530692)或从 SVN 同步下载