I have written an MDI based application, in which the child forms are of different types. I have now come across a situation where I need one child form to send a message to another child form, telling it to update itself. The first child form is unaware of whether the second child form is being displayed at the moment.
I had thought of having the first child form (form A) send a message to the main MDI form (form 0), which could then check the list of MDI child forms currently being displayed on the screen. If the desired form (form B) is being displayed, then the main form could send a second message to this form (form B).
Unfortunately, I haven't been able to write successfully the code which would enable the first child form to signal the main form. How can a child form send a message to its owner?
TIA,
No'am
The owner of a form isn't necessarily another form. The Owner property is just TComponent, which could be anything, including nil. But if the owner is a form, you can send it a message like this:
if Owner is TForm then
SendMessage(TForm(Owner).Handle, am_Foo, 0, 0);
You might not need to know the owner, though. The MDI parent form is always the main form of the project, and the main form is always designated by Application.MainForm. Send a message to that form's handle.
SendMessage(Application.MainForm.Handle, am_Foo, 0, 0);
The list of MDI children will be in Application.MainForm.MDIChildren. Your child forms can check that list for themselves rather than have the MDI parent do it. Here's a function either of your forms can use to find instances of any MDI child class. (If the forms that want to communicate aren't MDI children, you can still use this technique, but instead of Application.MainForm.MDIChildren, search the Screen.Forms list.)
function FindMDIChild(ChildClass: TFormClass): TForm;
var
i: Integer;
begin
for i := 0 to Pred(Application.MainForm.MDIChildCount) do begin
if Application.MainForm.MDIChild[i].InheritsFrom(ChildClass) then begin
Result := Application.MainForm.MDIChildren[i];
exit;
end;
end;
Result := nil;
end;
Your first child class could use it like this:
var
SecondChild: TForm;
begin
SecondChild := FindMDIChild(TSecondChild);
if Assigned(SecondChild) then begin
SendMessage(SecondChild.Handle, am_Foo, 0, 0);
end;
end;
When window messages are sent to windows in the same thread as the sender (which is always the case for any two VCL forms), their handlers are called immediately while the sender waits for a response. That's just like an ordinary function call, so you might wish to skip the messages and make regular functions in your form classes. Then you could use code like this:
var
SecondForm: TSecondForm;
begin
SecondForm := TSecondForm(FindMDIChild(TSecondForm));
if Assigned(SecondForm) then begin
SecondForm.Foo(0, 0);
end;
end;
Another approach that is worth using here is to use interfaces rather than messages. The advantage is that interfaces are more specific... messages can easily be accidentally re-used, unless your very specific on where your message constants are located.
To use this model, first create a global unit (or add to an existing) the following interface declarations:
type
ISpecificSignal = interface
{type CTRL-SHIFT-G here to generate a new guid}
procedure PerformSignal(Handle:Integer);
end;
Then modify your MAIN form interface, adding following:
TYPE
TMainForm = Class(TForm,ISpecificSignal)
:
private
Procedure PerformSignal(Handle:Integer);
end;
and in the implementation of the PerformSignal procedure looks like the following:
Procedure TMainForm.PerformSignal(Handle:Integer);
var
i: Integer;
Intf : ISpecificSignal;
begin
for i := 0 to Pred(Application.MainForm.MDIChildCount) do begin
if Supports(Application.MainForm.MDIChild[i],ISpecificSignal,Intf) and
(Application.MainForm.MDIChild[i].Handle <> Handle) then
Intf.PerformSignal(Handle);
end;
In your child form which ultimately must handle the signal, perform the same steps as you did for the main form, but change the PerformSignal to invoke the code you desire. Repeat as needed.
In the form which needs to actually start the process add the following code:
procedure DoSomething;
var
Intf : ISpecificSignal;
begin
if Supports(Application.MainForm,ISpecificSignal,Intf) then
Intf.PerformSignal(Handle);
end;
The largest advantage to this approach is your not limited to what parameters are passed (the interface can have any number of parameters, or no parameters), and it works without having to exercise the message pump.
EDIT Added Handle to avoid a situation where the existing form also needs the same notifications from other forms.
I don't know that specific of your problem, so this might not apply to your situation.
I guess your situation is FormA edit some value that affects FormB "rendering". The way I usually deal with this kind of situation is by making the value change to trigger an event. If more than 1 component in the system needs to be modified, I use a "multicasting" event.
A simple multicaster mechanism looks like this.
TMultiCastNotifyEventReceiver = class(TComponent)
private
FEvent : TNotifyEvent
public
property Event : TNotifyEvent read FEvent write FEvent;
end;
TMultiCastNotifyEvent = class(TComponent)
private
//TList or TObjectList to keep a list of Listener.
//Housekeeping code to make sure you don't keep reference to dangling pointers (I derived from TComponent to have access to the FreeNotification mechanis
public
procedure DoEvent(Sender : Tobject); //Same parameters as TNotifyEvent
function AddListener(NotifyEvent : TNotifyEvent) : TMultiCastNotifyEventReceiver
end;
That way, your formA doesn't need to know it's parent... Doesn't need to know FormB. FormB doesn't need to know FormA. Requirement for this to work though is that the FormA AND FormB must know the Value, and Value needs to know it needs to send a notification when it's modified. Usually results in better modularisation and encapsulation.
Then again, I put a lot of assumption about the nature of the problem you were trying to fix. I hope this helps anyway.
Why not just send the message to Application.Mainform.Handle, then in the Main form loop from 0 to MDIChildcount and resend the message to each one. Then, repond to the specific message only in the form class you want. I hope this serves your needs.
Related
I need a procedure that recreates a form.
The reason being is that I have forms with many different components. These component values (edit box text, checkbox checked or not, etc) are saved inside onhide and loaded again isnide onshow. This makes sure all user settings are retained between runs of the program.
The problem comes when they make a change (intentionally or otherwise) that leads to problems. I want to be able to "reset" the form back to the default settings when the application is first installed.
I can create a reset button that runs this code
FormName.free;
DeleteFile('FormNameSettings.ini');
FormName:=TFormName.Create(Application);
FormName.show;
That does what is required. The form is closed, clears the settings file (so states are not restored when it shows again) and then recreates the form. The form now has the original default settings.
My problem is trying to get that code into a function that I can call easily from multiple forms.
procedure ResetForm(form:tform;filename:string);
begin
form.free;
if fileexists(filename)=true then deletefile(filename);
<what goes here to recretae the form by the passed tform?>
end;
Can anyone help get that ResetForm procedure working? Latest Delphi 11.
To return the newly created form we actually need a var parameter for the form, but that alone is not very elegant, because one cannot pass a derived form class to a var parameter of type TForm and has to do a hard cast to please the compiler. Even using a function that returns a TForm is not much better as the result is most likely assigned to a variable of a derived form class and that would also be rejected by the compiler.
Thanks to generics we can write some code that overcomes these restrictions. As standalone generic procedures or functions are not supported in Delphi, we wrap it inside a record declaration:
type
TFormUtils = record
public
class procedure ResetForm<T: TForm>(var form: T; const filename: string); static;
end;
We also need to save some information about the form for later use:
the owner of the form
is the form currently showing
This allows to recreate the form.
class procedure TFormUtils.ResetForm<T>(var form: T; const filename: string);
begin
var formOwner := form.Owner;
var formShowing := form.Showing;
form.free;
if fileexists(filename) then
deletefile(filename);
form := T.Create(formOwner);
if formShowing then
form.Show;
end;
This is just for learning sake : Using accepted answer in this example Delphi application with login / logout - how to implement?,how can you show a specific form (if you have many forms) ?
I have placed a query on that LoginForm (which displays various forms that can be selected and shown).
How should I change the existing code so I can accomplish this maintaining the same functionality meaning that whatever form I select and click 'open' it will become the mainform and be shown?
The main form is the first form created using Application.CreateForm. So use an if or case statement to conditionally switch which form is created that way.
In pseudo-code:
if Form1 is main form then
Application.CreateForm(TForm1, Form1)
else if Form2 is main form then
Application.CreateForm(TForm2, Form2)
....
There are quite likely slicker ways to do this, but this is the basic principle.
So, you might well make a function to return the class of the main form, using whatever mechanism you have to choose it:
function GetMainFormClass: TFormClass;
begin
Result :=...; // your logic here
end;
Then in the .dpr file you write
Application.CreateForm(GetMainFormClass, MainForm);
You seem, to be really struggling with this. I'll try to spell it out in a little more detail. The implementation of GetMainFormClass might look like this:
function GetMainFormClass: TFormClass;
var
FormClassName: string;
begin
FormClassName := ...; // somehow get the name of the form class, e.g. 'TForm1'
if FormClassName = 'TForm1' then
Result := TForm1
else if FormClassName = 'TForm2' then
Result := TForm2
else
raise EUnrecognisedFormClassName.Create(...);
end;
Personally, I don't understand why you are presenting a grid control to the user. To me that seems like a very weak user interface with which to make a selection. I would present a radio group. Then you could write code like this:
type
TMainFormType = (mftForm1, mftForm2, mftForm3);
const
MainFormClass = array [TMainFormType] of TFormClass = (
TForm1,
TForm2,
TForm3
);
Then you can have a function that returns TMainFormType, by way of showing the user a form and presenting a radio group with three buttons. You can convert the radio group's ItemIndex into the appropriate TMainFormType value.
function GetMainFormType: TMainFormType;
begin
// ... your UI shows now, presenting the radio group
Result := TMainFormType(RadioGroup.ItemIndex);
end;
And then in the .dpr file you write
Application.CreateForm(MainFormClass[GetMainFormType], MainForm);
There are lots of different ways to do this, but you do need to understand the basics of branching code. Either if statements, or lookup tables. If you don't understand how these work then, with all due respect, you need to brush up those skills before you proceed.
Originally the question had to be how to access the components in the first place, however I somehow managed to figure it out. I am just learning Delphi so I am prone to dumb and obvious questions. I am also at a stage when I'm not actually writing anything useful, but just messing with random stuff to see how it works and maybe learn something.
Wall of text incoming, i want to explain what i am exploring at the moment...
Basically i have a form1 with a button1, pressing it creates a frame2, that frame2 has a button2, pressing button2 creates a frame3 within frame2 (it being frame3's parent and owner). Each frame has an another freeandnil-button. Upon pressing each button1/2/3, it gets disabled to prevent creating multiple instances. My original problem was that after using freeandnil-button, I couldnt access the button on the previous frame (it worked fine for forms, form1.button1.enabled:=true works fine from within frame2) that got disabled in order to re-enable it (frame2.button1.enabled:=true from within frame3 creates an access violation, I think).
Suppose I write something in the future that requires such communication? So I added an editbox to each frame, with a button on the other to change the editbox text, this is my current working solution:
procedure TFrame2.Button3Click(Sender: TObject);
var i,z:integer;
begin
for i := 0 to ComponentCount - 1 do
if components[i] is tframe3 then
for z := 0 to (components[i] as tframe3).ComponentCount - 1 do
if (components[i] as tframe3).Components[z] is TEdit then
((components[i] as tframe3).Components[z] as TEdit).Text:='ping';
end;
and
procedure TFrame3.Button3Click(Sender: TObject);
var i:integer;
begin
for i := 0 to parent.ComponentCount-1 do
if parent.components[i] is TEdit then
(parent.Components[i] as TEdit).Text:='pong';
end;
If I have a bunch of editboxes or whatever the hell, I suppose I could use Tag property to identify them, however, this much component counting and passing something AS something doesn't really look right or efficient enough to me.
My questions at the moment are: can it be done in a better way? and can someone provide the reasoning why cant I access "parent-frame" components from a "child-frame" in a dumb way (ie: frame2.button1.enabled:=true from within frame3)?
A couple of points:
Controls/Components are usually set to be owned by the form/frame that controls their lifetime and parented to the control that shows them. So when you create a TEdit to sit on a TPanel on a TForm, you would set TForm as the owner and TPanel as the parent of the TEdit. With frames you do the same: the frame is the owner of all controls on it, controls are parented to whatever container on the frame (can be the frame itself) is holding and and is thus reponsible for showing (painting) it.
When you iterate over the children of a control, iterating over Components uses the Owner relation; iterating over Controls uses the Parent relation. So your code above would already do a lot less if it were to iterate over the Controls.
Of course you can reference other controls in a "dumb" way, but you will have to provide for the access method. If you want others (not just child frames) you will at the very least have to declare a method to retrieve that control. Many ways to do this. One is to "ask" the form when you need it (using a method on the form), or have the form set a property on the frame when it is created...
Avoid accessing form's and frame's published properties. They are mainly there for Delphi's streaming system. When you tie your application together using your approach described above it can very soon become one incredibly tangled mess...
Example coming up (sometime this evening), it will take me a bit of time to also explain and I have work to do...
The following example deals only with the ping-pong game between the frames. Freeing any form or frame from one of its own event handlers is not such a good idea. Use the Release method for that as it prevents the form/frame from processing messages after is was freed. (Plenty of questions about this on SO by the way). Also, when you do release a frame from one of its own buttons, you will need to take care that the frame that created it has a chance to nil the references it may have held to that frame otherwise you are setting yourself up for some interesting to debug mayhem. Look into "Notification" and "NotifyControls" and the automatic notification by forms and frames that is sent to their owner/parent so these can remove the control from their components/controls collection. In your example, if you were to release Frame3 from its own "FreeAndNil" button's OnClick event handler, you would have to make sure that the Frame2 responds to the deletion (I think) notification message and nil's any references it holds to Frame3 (besides the ones that will already be cleared automatically in the components/controls collections).
Now, the ping pong game. There are a couple of ways to go about this.
Method 1
The first way is what you already tried with a loop over the components of the other frame. While it certainly is a way to avoid having to "use" the other frame, it is cumbersome and not very concise. Plus when you get more controls on your forms/frames, you will have to add a check on the name to know that you have the correct TEdit. And then you might just as well use the name directly, especially as one frame already has the other frame in its uses clause because it is creating it.
// PingFrame (your Frame2)
uses
...
Pong_fr;
type
TPingFrame = class(TFrame)
...
procedure CreateChildBtnClick(Sender: TObject);
procedure PingPongBtnClick(Sender: TObject);
private
FPong: TPongFrame; // This is the "extra" reference you need to nil when
// freeing the TPongFrame from one of its own event handlers.
...
end;
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
end;
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Edit1.Text := 'Ping';
end;
And on the other end:
// PongFrame (your Frame3)
type
TPongFrame = class(TFrame)
...
procedure PingPongBtnClick(Sender: TObject);
end;
implementation
uses
Ping_fr;
procedure TPongFrame.PingPong1BtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Edit1.Text := 'Pong called';
end;
This method seems all nice and dandy, but it has drawbacks:
You need to use the unit that holds the TPingFrame (Ping_fr in this example). Not too bad I guess, but... as Ping_fr already uses Pong_fr (the unit holding TPongFrame), you can't have Ping_fr using Pong_fr in the interface section and have Pong_fr using Ping_fr in the interface section as well. Doing so would have Delphi throwing an error because of a circular references. This can be solved by moving one of the uses to the implementation section (as done in the example for the use of Ping_fr in the Pong_fr unit.
A bigger drawback is that there is now a very tight coupling between the two frames. You cannot change the TEdit in either one to another type of control (unless that happens to have a Text property as well) without having to change the code in the other unit as well. Such tight coupling is a cause of major headaches and it is good practice to try and avoid it.
Method 2
One way to decrease the coupling between the frames and allow each frame to change it controls as it sees fit is not to use the controls of another form/frame directly. To do so you declare a method on each frame that the other can call. Each method updates its own controls. And from the OnClick event handlers you no longer access the other frame's controls directly, but you call that method
type
TPingFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Ping;
end;
procedure TPingFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
And on the other end:
type
TPongFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Ping;
end;
procedure TPongFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
This is better than Method 1 as it allows both frames to change their controls without having to worryabout "outsiders" referencing them, but still has the drawback of the circular reference that can only be "solved" by moving one "use" to the implementation section.
It is a good practice to try and avoid circular references altogether instead of patching them by moving units to the implementation section's uses clause. A rule of thumb I use is:
Any form/frame may know and use the public interface of the forms/frames it instantiates (though it should avoid the controls in the "default visibility" part of that interface), but no form/frame should not have any knowledge of the specific form/frame that created it.
Method 3
One way of achieving this (there are many) it to use events just like the TButton's OnClick event.
On the TPongFrame side of things you can remove the use of Ping_fr. You no longer need it.
Then you need to declare a property and the field it references and declare a method to fire the event.
type
TPongFrame = class(TFrame)
private
FOnPingPongClicked: TNotifyEvent;
protected
procedure DoPingPongClicked;
public
property OnPingPongClicked: TNotifyEvent
read FOnPingPongClicked write FOnPingPongClicked;
end;
The Ping method stays and its implementation is unchanged. The PingPongBtnClick event handler also stays, but its implementation now becomes:
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
DoPingPongClicked;
end;
procedure TPongFrame.DoPingPongClicked;
begin
if Assigned(FOnPingPongClicked) then
FOnPingPongClicked(Self);
end;
You could put the code from DoPingPongClicked straight in here, but it is a good practice to fire an event in a separate method. It avoids duplicating the exact same code if you would have an event that can be fired from multiple parts of your code. And it also allows descendants (when you get those) to override the "firing" method (you'll have to mark it virtual in the ancestor) and do something specific all the times the event is fired.
On the TPingFrame side of things you need to code a handler for the new event:
type
TPingFrame = class(TFrame)
...
protected
procedure HandleOnPingPongClicked(Sender: TObject);
Note that the signature of this handler needs to be what the TNotifyEvent specifies. And of course you need to ensure that this event handler gets called when the event fires in TPongFrame:
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
// Listen to event fired by PongFrame whenever it's PingPingBtn is clicked
FPong.OnPingPongClicked := HandleOnPingPongClicked;
end;
In the handler code you can do what you need to do. It can be general:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
begin
Edit1.Text := 'OnPingPongClicked event was fired';
end;
And of course you can also use the fact that the event passes a reference to the instance firing the event as well:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
var
Pong: TPongFrame;
begin
// This checks that Sender actually is a TPongFrame and throws an exception if not
Pong := Sender as TPongFrame;
// Use properties from the Pong instance that was passed in
Edit1.Text := 'OnPingPongClicked event was fired by ' + Pong.Name;
end;
Enjoy!
I'm was trying to write a dll library in Delphi wih a function that creates an instance of a TFrame descendant and returns it. But when I imported this function in an application, every time I called it I would get an exception like "the 'xxx' control has no parent window". I'm not 100% sure, but the exception appeared in the constructor of that class when any of GUI controls was accessed.
Could you please tell me what the reason of that behaviour is? Should I just use TForm descendants instead or is there a better solution?
Thank you!
About the error
That error message is raised from the Controls.pas unit, from the TWinControl.CreateWnd method. Essentially that code is used to create the Window handle for your TWinControl descendant (TFrame, TButton, TEdit... if it can have keyboard focus it's an TWinControl descendant), and it's actually an very sensible error message: You can't have a Window without an WindowParent, and since we're talking about the VCL here, it makes a lot of sense to try and get the parent window handle from TWinControl.Parent; And that's not assigned.
That's not WHY the error message is popping up. You get to see that error message because some of the code you're using to set up the frame requires an Window handle for some operation. It could be anything, like setting the Caption of some component (that internally requires an window handle do to some calculation). I personally really hate it when that happens. When I create GUI's from code I try to delay the assignment of Parent as much as possible, in an attempt to delay the creation of the window, so I got bitten by this many times.
Specific to your DLL usage, possible fix
I'm going to put my psycho mind reader hat on. Since you need to return a FRAME from your DLL, and you can't return the actual Frame because that's an Delphi-specific object and you're not allowed to return Delphi-specific objects over DLL boundaries, my guess is you're returning an Window Handle, as all the nice API's do, using a function definition like this:
function GiveMeTheNiceFrame:HWND;
The trouble is, that routine requires the creation of the actual Window Handle, by a call to TWinControl.CreateWnd, and in turn that call requires an parent window handle to set up the call to Windows.CreateWindowEx, and the routine can't get an parent window handle, so it errors out.
Try replacing your function with something allong the lines of:
function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND;
begin
Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle;
end;
... ie: use the CreateParented(AParentWindow:HWND) constructor, not the usual Create(AOwner:TComponent) and pass an owner HWND to your DLL.
There are a few important things to remember:
When using DLLs, both your DLL and your EXE each have an Application instance that are struggling for control. The Controls in your DLL will see the Application instance that belongs to the DLL; the Controls in your EXE will see the Application instance that belongs to the EXE. That struggle is not there when using packages, as then there will only be one Application instance.
Frames are Controls, but they are not Forms.
When using Controls in an application, they cannot visually exist without a parent Control (usually a Form or a container that has a parent hierarchy towards a Form).
Some Controls cannot expose their full functionality unless they exist visually and have a valid parent.
Try to reproduce your problem inside the EXE; if you cannot reproduce, it is probably the first thing in the above list.
--jeroen
Sounds like you simply need to assign the component (a form or part of a form, like a panel) that holds the frame to theframe.parent.
You cannot do GUI work before it is assigned. Frames are parts of forms for reuse, and normally need to assign some parent to them.
Move the GUI code to onshow or a procedure you call explicitely, so that the calling code can assign parent.
Or make the parent a parameter in the function.
I found this (CreateParams is called as part of CreateWnd):
procedure TCustomFrame.CreateParams(var Params: TCreateParams);
begin
inherited;
if Parent = nil then
Params.WndParent := Application.Handle;
end;
And Application.Handle = 0 so it always throws the error later in CreateWnd.
After reading this
Delphi: How to call inherited inherited ancestor on a virtual method?
I have solved it by overriding CreateParams in my frame to miss out the tCustomFrame version:
type
tCreateParamsMethod = procedure(var Params: TCreateParams) of object;
type
tMyScrollingWinControl = class(TScrollingWinControl);
procedure TDelphiFrame.CreateParams(var Params: TCreateParams);
var
Proc: tCreateParamsMethod;
begin
TMethod(Proc).Code := #TMyScrollingWinControl.CreateParams;
TMethod(Proc).Data := Self;
Proc(Params);
end;
Now it's just throwing errors when trying to set the focus on subcontrols, which I think I will fix by intercepting WM_FOCUS but we'll how it goes from here.
function CreateFrame(hwndParent: HWnd): HWnd; stdcall;
var
frame: tFrame;
begin
Result := 0;
try
frame := TDelphiFrame.CreateParented(hwndParent);
Result := frame.Handle;
except on e: Exception do
ShowMessage(e.Message);
end;
end;
You can avoid this message by assigning nil to the parent OnClose event, sometimes it works:
SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
I think this is very cool solution. I think it is not tried before :)
I'm using a Dummy Parent (which is a Form).
function MyFrame_Create(hApplication, hwndParent:THandle; X, Y, W, H:Integer):Pointer; stdcall;
var Fr: TMyFrame;
F: TForm;
CurAppHandle: THandle;
begin
CurAppHandle:=Application.Handle;
Application.Handle:=hApplication;
//---
F:=TForm. Create(Application);//Create a dummy form
F.Position:=poDesigned;
F.Width:=0; F.Top:=0; F.Left:=-400; F.Top:=-400;//Hide Form
F.Visible:=True;
//---
Fr:=TMyFrame.Create(Application);
Fr.Parent:=F;//Set Frame's parent
//Fr.ParentWindow:=hwndParent;
Windows.SetParent(Fr.Handle, hwndParent);//Set Frame's parent window
if CurAppHandle>0 then Application.Handle:=CurAppHandle;
//---
Fr.Left:=X;
Fr.Top:=Y;
Fr.Width:=W;
Fr.Height:=H;
Result:=Fr;
end;//MyFrame_Create
procedure MyFrame_Destroy(_Fr:Pointer); stdcall;
var Fr: TMyFrame;
F: TObject;
begin
Fr:=_Fr;
F:=Fr.Parent;
Fr.Parent:=Nil;
if (F is TForm) then F.Free;
//SetParent(Fr.Handle, 0);
//Fr.ParentWindow:=0;
Fr.Free;
end;//MyFrame_Destroy
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?
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...
procedure TDiferentForm.DoSomething();
begin
Form1.YourFrame.ButtonClick(nil);
end;
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
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;