Fake modal dialog using Show?

2019-01-24 17:29发布

问题:

My application have several modules, each in one tab on the mainform. When using a dialog it is convenient to call ShowModal because you know when the dialog is finished. But for the user it is not good as it lock the whole program until the dialog closes.

I want to have a locally modal dialog. So one module can open a dialog and it locks current module only. The user can still switch to another module and continue to work. If the user return to the first module the dialog is there waiting for close before the user can continue to work in that module.

I have to make some kind of framework for this that all dialogs in the application can use. I have a baseclass for all dialogs TAttracsForm and I think here is the place to add my Show() method.

This should lock access to all wincontrols only in the current module. It should simulate a call to ShowModal(). How can I achieve this ?

Regards

回答1:

You will have to do the following:

  1. Have an identity for each module
  2. Have a flag that is active or inactive for each module
  3. Have a flag that stores the modality of the attached dialog. If it is modal and the module is active, then call the show method in the appropriate eventhandler. Remember to update these values in the onshow and onclose events of each dialog.

You may have to fine tune this suggestion till you achieve the exact functionality that you require.



回答2:

Do you still want to implement this with "you know when the dialog is finished" metaphor? So like

DoSomethingBeforeDialog(); 
Form:=TFakeFormDialog.Create(Nil);
try
   Form.FakeShowModal();
finally
  Form.Free;
end;
DoSomethingAfterDialog();  

if the answer is yes, you would try to implement this with threads, like Google Chrome do this with tab pages. But without threads only you can catch message processing with a code like this

function TFakeModalDlg.FakeShowModal(FormParent: TWinControl): boolean;
begin
  Parent:=FormParent;
  SetBounds((FormParent.Width - Width) div 2, (FormParent.Height - Height) div 2,
    Width, Height);
  Show;
  while NoButtonIsPressed() do
  begin
    Application.HandleMessage;
  end;
  Hide;
end;

And you even have code below...

Form:=TFakeModalDlg.Create(Nil);
try
  (Sender as TButton).Caption:='Going modal...';
  Form.FakeShowModal(TabSheet1);
  (Sender as TButton).Caption:='Returned from modal';
finally
  Form.Free;
end;

called multiply times from your tabs, but the problem is the these "dialogs" should be closed in "stack order" i.e. reverse to the order they were showed. I think it's impossible to force users to close forms in developers preference order :)



回答3:

I have actually almost implemented local modal dialogs now. It is built around that when a TForms Enabled property is set To False the whole Form is locked from input. And my modules is just a descendant from TForm.

My ViewManager class that decide what modules is current add/close modules etc got 2 new methods. LockCurrentView and UnLOckCurrentView.

function TViewManager.LockCurrentView: TChildTemplate;
begin
  Result := CurrentView;
  Result.Enabled := False;
  Result.VMDeactivate;         // DeActivate menus and toolbas for this module
end;

procedure TViewManager.UnLockCurrentView(aCallerForm: TChildTemplate);
begin
  aCallerForm.VMActivate;           // Activate menus and toolbas for this module
  aCallerForm.Enabled := True;
end;

TAttracsForm is the baseclass of all dialogs. I Override FormClose and add a new method ShowLocalModal to call instead of ShowModal. I also have to add a TNotifyEvent OnAfterDestruction to be called when the dialog is closed.

procedure TAttracsForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if Assigned(fCallerForm) then      
  begin
    ClientMainForm.ViewManager.UnLockCurrentView(fCallerForm as TChildTemplate);

    if Assigned(OnAfterDestruction) then
      OnAfterDestruction(Self);

    Action := caFree;
  end;
end;

{ Call to make a dialog modal per module.
  Limitation is that the creator of the module must be a TChildtemplate.
  Several modal dialogs cannot be stacked with this method.}
procedure TAttracsForm.ShowLocalModal(aNotifyAfterClose: TNotifyEvent);
begin
  fCallerForm := ClientMainForm.ViewManager.LockCurrentView;    // Lock current module and return it
  PopupParent := fCallerForm;
  OnAfterDestruction := aNotifyAfterClose;
  Show;
end;

Some test with simple dialogs looks promising. So the module just have to call ShowLocalModal(myMethod) which have a TNotifyEvent as parameter. This method is called when the dialog is closed.