views:

140

answers:

2

I'm still working with my Local Modal Dialogs (LMD). See this question for more information. It works fine now for simple cases, but in sometimes there is a result in the dialog that I want to notify the caller. As the call is asynchronous with Show() I cannot simply get the result after the call.

So my question is how can I return one or several values from method TLMD_Dialog.btnOkClick to method TModule.myEvent?

I have 3 units involved in this: (Note that TLMD_Dialog inherits from TAttracsForm)

// Module.pas
procedure myEvent(Sender: TObject);

procedure TModule.btnCallDlg(Sender: TObject);
begin
  if Supports(lhaHandle.CurrentBoldObject, IObject, vMyObject) then
    TModalDialog.Execute(param1, param2, myEvent);
end;

procedure TModule.myEvent(Sender: TObject);
begin
  // Some code that react on result of the LMD dialog
end;

// AttracsForm.pas
type
  TAttracsForm = class(TForm)
  procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    fCallerForm: TForm;      // May be replaced by check PopupParent but a separate variable may be safer
    fOnAfterDestruction: TNotifyEvent;
  published
    procedure ShowLocalModal(aNotifyAfterClose: TNotifyEvent=nil);
  end;

procedure TAttracsForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if Assigned(fCallerForm) then      // fCallerForm not assinged means that ShowLocalModal is not called. The old way to show dialog is used
  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;

// LMD_Dialog.pas (inherit from TAttracsForm)
class procedure Execute(aParam: IBoldObject; aNotifyEvent: TNotifyEvent);
class procedure TLMD_Dialog.Execute(aParam: IBoldObject; aNotifyEvent: TNotifyEvent);
begin
  with Self.Create(nil) do
  begin
    // Do preparation
    ShowLocalModal(aNotifyEvent);
  end;
end;

procedure TLMD_Dialog.btnOkClick(Sender: TObject);
begin
  // Do something before close down
  // Set Result of the dialog
  Close;
end;
+2  A: 

It's quite simple really, you don't use TNotifyEvent but a custom event type with additional parameters for the information you want to return.

A simple example for getting a file name and another parameter (for example the name of a ZIP file and its compression level:

type
  TReturnSaveZipFileDataEvent = procedure(Sender: TObject;
    const AFileName: string; ACompressionLevel: Cardinal) of object;

Now instead of declaring the last parameter of your Execute() method to be of type TNotifyEvent you declare it to have your special event type.

Note that an IMHO much better way to implement such functionality would be to use interfaces. A custom interface is passed to the dialog, which can use it to do more than just call back with the results. For example the interface could have another method to check for the validity of the entered data, which the dialog would call in the OnCloseQuery handler.

mghie
Thanks for the fast answer!But that means I cannot reuse my FormClose method in TAttracsForm ?I must overide this in TLMD_Dialog and use the custom event you described.About the interfaces I have not experience of how to use that. I have to study how to use them.
Roland Bengtsson
Well, you can make a protected method in `TAttracsForm` that does what your current handler does, and which you call from your event handler in the descendant class. Then it's only one line to add. The base class doesn't need to have an implementation for the handler itself.
mghie
+1  A: 

What I do occasionally, is add an event to the base class (in your case form), like:

//untested code, no doubt there are errors in it, it's the idea I want to pass

type
  TEventData = class(TObject)
  public
    property SomeCommonFieldForAncestorAndDescendants: String;
  end;

  TSomeBaseEvent = procedure (ASender: TObject; AEventData: TEventData) of object;

  TSomeBaseClassOrForm = class(TForm)
  protected
    FSomeEventData: TEventData;

    function GiveSomeDataClass: TClass; virtual;
    procedure DoOnSomeThing; virtual;
  public
    constructor Create; override;

    property OnSomething: TSomeBaseEvent;
  end;

Now you could let the constructor instantiate a TEventData or descendant bij calling GiveSomeDataClass.Create; Your descendant only has to override GiveSomeDataClass and return the descendant of TEventData it wants to use. Now you have a single event, declared in the ancestor, returning a sender and a data object and the latter may vary in types of data it contains. The handler can now use if (AEventData is TEventSpecialData) and act accordingly. Before calling DoOnSomething you can set the values of FSomeEventData, even in a DoOnSomeThing override in a descendant, whatever. ;-)

Alternative: You can make the event-data a public property and use Sender from a regular event to step to this property from the handler.

MvdH