tags:

views:

1880

answers:

5

Hi Excuse me if this is simple - I'm new to programming... I need to run an action that is attached to a button (say SQLBtn) that is placed on a Frame1 within my app, from Form1. I have included the frame in Form1 uses, but can't seem to address in any way. I've tried Frame1.SQLbtn TFrame1.SQLbtn TFrameSQLBtn etc but can't get to it. I would like to get to something similar to 'SQLbtn.click' to run the event behind it. Does any one have any ideas how to address it please? Den

+5  A: 

I am not sure I understand your question correctly. Sounds like you have a frame with a button (and either an TAction or click event handler on the button) and this frame is sitting on a form. Now you want to programmatically simulate a click on that button.

Obviously you need to add the frame unit to your form's uses clause. You also need an instance of the frame on the form which should lead to a form field of the frame type, e.g.

TForm1...
  ...
  Frame1: TFrame1;
end;

Then you can execute that code via Frame1.SQLbtn.Click from within any of the form's methods. A better way would probably be to provide a public method on the frame which you can use from the form. Then you don't need to access the button directly (the button is an implementation detail of the frame, frame private so to speak).

Edit after clarification

I understand you have the following scenario:

TFrameForm1...
  ...
  Frame1: TFrame1;
end;

TForm1...
  ...
  procedure something;
end;

procedure TForm1.something;
begin
  // how to call a method on Frame1 which is on FrameForm1
end;

Your best choice is to move the code from frame button OnClick event handler into a separate unit. This can be a datamodule, or a just another unit with a standalone procedure. Then you can call that code from both Form1 and the Frame1 button event handler. This is what Vegar has commented.

If that is not possible, e.g. because the processing requires access to other controls on Frame1, move the code into a new procedure on Frame1 (my original suggestion):

TFrame1...
  ...
public
  procedure framestuff;
end;

procedure TFrame1.framestuff;
begin
  ...
end;

procedure TFrame1.SQLbtnClick(Sender...);
begin
  framestuff;
end;

Now you need to call that method from Form1. You'll need a reference to FrameForm1 for that. Which you need to initialize manually (!) when you create TFrameForm1. In this example, the reference is a field FFrameForm:

TForm1...
  ...
  FFrameForm: TFrameForm1;
  procedure something;      
end;

procedure TForm1.something;
begin
  FrameForm.framestuff;
end;

Or, by default Delphi adds global variables for all forms to the form units (auto form creation, check project options / forms). Then you do this:

procedure TForm1.something;
begin
  FrameForm1.framestuff; // if FrameForm1 is the name Delphi used for the global variable
end;

Of course there are many other variations...

deepc
Totally agree. Simulate userinput to trigger actions is certainty an indicator on bad design. Generally, the code you have in the onClick-event should be extracted out of the GUI and into some businesslayer. Than the the mainform can call the same method as the OnClick-event does.
Vegar
A: 

Hi Yes, you've essentially understood it - though one complication. I want access from a different form than the one the frame is on. i.e. Frame1 with the button, is on (say) FrameForm1. From another form - Form1 - I want to access and run the routine stored behind the button on Frame1/FrameForm1. I'm working on putting an instance of the frame on Form1 and then hiding it - but not too practical, and also throwing a compile error. Your second suggestion sounds like it might fit the bill (public mmethod) so I'll go investigate public methods. (I'm new to all this...) Really appreciate your response and help, thanks.

a3dvm
A little advice (not regarding your problem): clarifications of the question should always be added to original question, not go into a self-answer. This way everyone who reads the question for the first time gets all the facts.
deepc
+1  A: 
procedure TDiferentForm.DoSomething();
begin
  Form1.YourFrame.ButtonClick(nil);
end;
dmajkic
A: 

One thing that might help you understand: when you create an instance of a form (or frame), delphi goes through the DFM and creates instances of all the objects described there.

IF you have a variable in the form's definition that matches the name of the object in the DFM, the loader will make the variable point to the object; if you don't have a variable, the object is created but you would have to iterate through .Components or .Controls to get to it.

If the form has an instance variable of the frame (and that variable is public), then any other form's code can access it (e.g. MainForm.Frame1...) and do what it wants to.

To encapsulate the frame, the form (which is, after all just a class) can have public properties that have accessors and mutators to proxy the information to and from the embedded frame. Encapsulation is good (IMHO the most important aspect of OOP) because it makes the link between the caller and the frame loose: you can change either side a lot without breaking things.

Cheers

Richard Haven
A: 

Another solution is to use interfaces to avoid the circular reference problem and simplify the code a bit. Lets say that you have a procedure named foo that you want to invoke from anyplace in the system. The implementation of this procedure is in tFooForm which is not the main form, but a form that the main form knows about.

First create a new unit and call it *Foo_Intf.pas*

Its contents will be the following:

unit Foo_Intf;

interface

type
  IFoo = interface
    ['{4AC12AB9-557B-4E61-AB2D-8B10E591E33A}'] 
    // CTRL-SHIFT-G to create a new guid
    procedure Foo;
  end;

implementation

end.

then add the method to the tFooForm class, and also include the interface. Don't forget to use the foo_intf.pas unit in your interface uses clause. Implement the foo class to do what ever you want that procedure to perform.

tFooForm = class(tForm,IFoo)
  :
  procedure Foo;
  :
end;

Also add the IFoo interface to the main form, exactly like the previous step but change the implementation to be the following:

procedure tMainForm.Foo;
begin
  if not Assigned(FooForm) then
    FooForm := tFooForm.Create(Application);  // for simplicity sake
  FooForm.Foo;
end;

Now, anyplace you want to call the foo function, just include ONLY the Foo_Intf unit in the uses clause and use the following snippit:

var
  FooIntf : IFoo;
begin
  if Not Supports(Application.MainForm, IFoo, FooIntf) then
    Raise Exception.create('Application.mainform does not implement IFoo');   
  FooIntf.Foo;
end;
skamradt