accessing components of frame1 with frame2, with frame2 being on frame1, in delphi, frames are created dynamically - delphi

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!

Related

The control 'xxx' has no parent window

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

Maintain UpDown-Associate connection while recreating the associate

I have an TUpDown control whose Associate is set to an instance of a TEdit subclass. The edit class calls RecreateWnd in its overriden DoEnter method. Unfortunately this kills the buddy connection at the API level which leads to strange behavior e.g. when clicking on the updown arrows.
My problem is that the edit instance doesn't know that it is the buddy of some updown to which it should reconnect and the updown isn't notified of the loss of its buddy. Any ideas how I could reconnect the two?
I noticed how TCustomUpDown.SetAssociate checks that updown and buddy have the same parent and uses this to avoid duplicate associations. So I tried calling my own RecreateWnd method:
procedure TAlignedEdit.RecreateWnd;
var
i: Integer;
c: TControl;
ud: TCustomUpDown;
begin
ud := nil;
for i := 0 to Pred(Parent.ControlCount) do
begin
c := Parent.Controls[i];
if c is TCustomUpDown then
if THACK_CustomUpDown(c).Associate = Self then
begin
ud := TCustomUpDown(c);
Break;
end;
end;
inherited RecreateWnd;
if Assigned(ud) then
begin
THACK_CustomUpDown(ud).Associate := nil;
THACK_CustomUpDown(ud).Associate := Self;
end;
end;
et voila - it works!
You've discovered something rather unfortunate. You set up an association between two controls at the application level, so you should be able to continue to manage that association in application-level code, but the VCL doesn't provide the framework necessary for maintaining that. Ideally, there would be a generic association framework, so associated controls could notify each other that they should update themselves.
The VCL has the beginnings of that, with the Notification method, but that only notifies of components being destroyed.
I think your proposed solution is a little too specific to the task. An edit control shouldn't necessarily know that it's attached to an up-down control, and even if it does, they shouldn't be required to share a parent. On the other hand, writing an entire generic observer framework for this problem would be overkill. I propose a compromise.
Start with a new event property on the edit control:
property OnRecreateWnd: TNotifyEvent read FOnRecreateWnd write FOnRecreateWnd;
Then override RecreateWnd as you did above, but instead of all the up-down-control-specific code, simply trigger the event:
procedure TAlignedEdit.RecreateWnd;
begin
inherited;
if Assigned(OnRecreateWnd) then
OnRecreateWnd(Self);
end;
Now, handle that event in your application code, where you know exactly which controls are associated with each other, so you don't have to search for anything, and you don't need to require any parent-child relationships:
procedure TUlrichForm.AlignedEdit1RecreateWnd(Sender: TObject);
begin
Assert(Sender = AlignedEdit1);
UpDown1.Associate := nil;
UpDown1.Associate := AlignedEdit1;
end;
Try storing the value of the Associate property in a local variable before you call RecreateWnd, then setting it back afterwards.

Under what conditions will a TForm fire OnResize on show?

As an extension of this question:
TForm.OnResize is sometimes fired before a form is first shown, but not always. For example, if BorderStyle is either bsDialog or bsNone, then OnResize will not fire. For all other BorderStyle values (and with all other properties at their defaults), OnResize does fire.
Are there other things that affect whether OnResize will fire before the form is shown? For example, other properties, or combinations of properties, that can affect this?
The OnResize event is a result of the ShowWindow API function sending a WM_SIZE message to the window. That bears repeating: the message is coming from Windows, not from Delphi. It's a Windows function (ShowWindow) that's (sometimes) sending the message that triggers the event -- so the VCL source code is not really helpful in this case.
Bonus points for definitive answers based on documented ShowWindow / WM_SIZE behavior, e.g. references to MSDN documentation or Petzold books.
Maybe it even depend on user's display settings or desktop theme or Windows version. If OnResize were giving me problems like this, I would build my program to always expect it and handle it in any situation, no matter what I think to be the cause.
I believe that OnResize will fire when an event dispatch a message
saying that form size (left, bottom, width, height) will be modified.
Since you already discovered which message fires that event, you need
now trace where the message is sent in the vcl.
Look at the vcl source code to see if you can spot those operations.
Edit: let's go low level. Forms in windows (grossly talking) have what
is called "window class" (it's not a class like we know it oop). All times the window class of the form is resized (and form is visible), the WM_SIZE is sent.
So it will not happen all the times the form is shown, but only the it's dimensions are changed compared with underlying window class.
As you have observed, many properties valuez change the dimensions of the form (even a few pixels).
This is a very superficial explanation, that's a ton of other details - but it's my understanding how things works "under the hood".
There's no substitute for testing. How about creating a form in code, setting the properties you're interested in and recording when the resize event is called.
If you'll excuse the ugliness of the code, here's a rough proof of concept that tests all combinations of BorderStyle and Position without explicitly coding for each one. You can add more properties and take it as far as you like. A tool like CodeSite would make the logging cleaner and easier, too.
Create an application with 2 forms. Make sure the second one isn't auto-created.
In the second form, add a property and add a little logging code to the form's Resize event:
private
FOnResizeFired: TNotifyEvent;
public
property OnResizeFired: TNotifyEvent read FOnResizeFired write FOnResizeFired;
end;
...
procedure TForm2.FormResize(Sender: TObject);
begin
if Assigned(FOnResizeFired) then
FOnResizeFired(self);
end;
In the main form, add TypInfo to the uses clause and drop a button and a memo on the form.
Add a simple procedure:
procedure TForm1.ResizeDetected(Sender: TObject);
begin
Memo1.Lines.Add(' *** Resize detected');
end;
Now add the following to the ButtonClick event:
procedure TForm1.Button1Click(Sender: TObject);
var
lBorderStyle: TFormBorderStyle;
lBorderStyleName: string;
lPosition: TPosition;
lPositionName: string;
lForm: TForm2;
begin
Memo1.Clear;
for lBorderStyle in [low(TFormBorderStyle) .. high(TFormBorderStyle)] do
begin
for lPosition in [low(TPosition) .. high(TPosition)] do
begin
lBorderStyleName := GetEnumName(TypeInfo(TFormBorderStyle), Integer(lBorderStyle));
lPositionName := GetEnumName(TypeInfo(TPosition), Integer(lPosition));
Memo1.Lines.Add(Format('Border: %s Position: %s', [lBorderStyleName, lPositionName]));
Memo1.Lines.Add(' Creating form');
lForm := TForm2.Create(self);
try
Memo1.Lines.Add(' Form Created');
lForm.OnResizeFired := ResizeDetected;
Memo1.Lines.Add(' Setting border style');
lForm.BorderStyle := lBorderStyle;
Memo1.Lines.Add(' Setting Position');
lForm.Position := lPosition;
Memo1.Lines.Add(' Showing form');
lForm.Show;
Memo1.Lines.Add(' Form Shown');
lForm.Close;
Memo1.Lines.Add(' Form Closed');
finally
FreeAndNil(lForm);
Memo1.Lines.Add(' Form Freed');
end;
end;
end;
end;
You'll notice that resize fires when some properties are set before the form is shown, and I see that in some combinations, resize seems to fire twice when the form is shown. Interesting.

How can a form send a message to its owner?

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.

Delphi - Accessing a Frame object from a Form

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;

Resources