Assigning a customized event to an Objects OnMouseEnter event - delphi

I am looking for a way to reroute the event handler for OnMouseEnter of TPanel. I have a class that contains an array of TPanels that I am using to represent a seating chart. I would like to be able to assign a custom event to the OnMouseEnter event of each of the TPanels which are created dynamically at runtime. I will provide some pseudo code to try and help clarify:
//Class Declaration
TMyClass = Class(TObject)
PanelArray : Array of TPanel;
end
//Forms declarations
MyClass : TMyClass //Created on the on create event of form.
//Loop assigning the new event
For I:=0 To High(MyClass.PanelArray) do
Begin
//Instance of panel
MyClass.PanelArray[I].OnMouseEnter := NewOnMouseEnterEvent(Parm1,Parm2,Parm3,....ParmN);
end;
When New OnMouseEnter event is called, in place of TPanel's OnMouseEnter I imagine it would function exactly the way it would if you had placed a panel on a form at design time and created an OnMouseEnter event from the object Inspector, but with the ability to pass additional parameters:
TForm1.Panel1OnMouseEnter(Sender: Tobject; Parm1,Parm2,Parm3,....ParmN:String);
begin
ShowMessage(Parm1);
end;
Can anyone suggest a possible solution to this? I hope I was clear enough, I am a first time poster. Thanks in advance for any help you can provide.

The OnMouseEnter property has type TNotifyEvent, which is a method pointer that expects exactly one parameter. You cannot assign pointers to other methods with other signatures.
The parameter you get is Sender, which is technically all you need. That tells you which panel is being acted on. Using that, you can look up whatever other values you need that should be associated with that panel. For example, you could define a record that holds all your other parameters, and then store them in a dictionary keyed off the panels:
type
TMouseEnterParameters = record
Param1, Param2, ..., ParamN: string;
end;
TMouseEnterDictionary = TDictionary<TPanel, TMouseEnterParameters>;
Populate the dictionary when you assign the event handlers:
for i := 0 to High(MyClass.PanelArray) do begin
MyClass.PanelArray[i].OnMouseEnter := Panel1OnMouseEnter;
ParamDict[MyClass.PanelArray[i]] := MakeParameters(Param1, Param2, ..., ParamN);
end;
Then you can find the associated values in the OnMouseEnter hander:
Params := ParamDict[Sender as TPanel];
If you Delphi version is too old to support a generic TDictionary class, you can use any number of other data structures, too. TDictionary just makes it easy.

Related

Custom Component TImage events causing error "Does not exist"

I have a custom component witht the following events
private
{ Private declarations }
...
fOnImageClick: TNotifyEvent;
fOnImageMouseUp: TMouseEvent;
fOnImageMouseDown: TMouseEvent;
fOnImageMouseMove: TMouseMoveEvent;
fOnImageMouseEnter: TNotifyEvent;
fOnImageMouseLeave: TNotifyEvent;
fOnImageSelect: TNotifyEvent;
fOnImageDblClick: TNotifyEvent;
protected
...
public
{ Public declarations }
...
published
...
property OnImageClick: TNotifyEvent read fOnImageClick write fOnImageClick;
property OnImageSelect: TNotifyEvent read fOnImageSelect write fOnImageSelect;
property OnImageDblClick: TNotifyEvent read fOnImageDblClick write fOnImageDblClick;
property OnImageMouseDown: TMouseEvent read fOnImageMouseDown write fOnImageMouseDown;
property OnImageMouseUp: TMouseEvent read fOnImageMouseUp write fOnImageMouseUp;
property OnImageMouseMove: TMouseMoveEvent read fOnImageMouseMove write fOnImageMouseMove;
property OnImageMouseLeave: TNotifyEvent read fOnImageMouseLeave write fOnImageMouseLeave;
property OnImageMouseEnter: TNotifyEvent read fOnImageMouseEnter write fOnImageMouseEnter;
end;
I assign them to a TImage whose parent is TPanel whos parent is TScrollBox
img:= TImage.Create(ThumbPnl);
img.Parent:= ThumbPnl;
img.Tag:= I;
img.Align:= alClient;
img.Stretch:= true;
img.OnClick:= fOnImageClick;
img.OnDblClick:= fOnImageDblClick;
img.OnMouseEnter:= fOnImageMouseEnter;
img.OnMouseLeave:= fOnImageMouseLeave;
img.OnMouseDown:= fOnImageMouseDown;
img.OnMouseUp:= fOnImageMouseUp;
img.OnMouseMove:= fOnImageMouseMove;
The component compiles and bulds just fine. The application with this component also compiles and runs jus fine. If I assign an OnClick event, it works. All other events, if I assign them and try to run the app, i get an error saying the event doesn't exist
Anyone know why that is?
You are making a fairly common mistake for new component creators. You are breaking this rule:
Component users write event handlers, component writers "fire" events
In your case what you are doing is assigning events when you as the component developer shouldn't be doing that. It's up to the component user to assign code to occur in the OnClick, OnDblClick, etc. events.
It's enough for you to declare the events as Published and as TNotifyEvent types.
property OnSomeEvent: TNotifyEvent read FOnSomeEvent write FOnSomeEvent;
That's all you need to do to create the events. Your job is to "fire" them; that is, make them happen.
That is done in your component at the appropriate moment. Usually, what you'll do is create a method DoXXXXX where XXXXX is the thing that is happening. So you'd create
procedure TMyImage.DoSomeEvent;
begin
if Assigned(FOnSomeEvent) then FOnSomeEvent(Self);
end;
Then, within your code, when the event should be fired, you simply call DoSomeEvent.
Now, if you want to enhance the functionality of the default events, then you need to override the method that fires the event.
I hate to do it, but my classic component TSmiley illustrates this simply and clearly:
http://tsmiley.svn.sourceforge.net/viewvc/tsmiley/
Take a look at that example and you should see how to create events.
What is an event
The event in Delphi is a method pointer. A method pointer is basically two pointers, one points to the method you assign to the event, and the other points to the live instance (object) you assign.
What I assume you're expecting
I think you expect the event in your inner object follows the events assigned to your outer object, and that will not happen automatically.
When you assign it, you're doing pointer assignments. So to take an example, your line
img.OnDblClick:= fOnImageDblClick;
Performs a pointer assignment with the value fOnImageDblClick have at the time. If it is nil, img.OnDblClick will be nil from now. If it points to Form1.MyComponentImageClick at the time, img.OnDblClick will point to the same method on the same object from now, but, if you later change where fOnImageDblClick points, the imgOnDblClick will not follow... it will remains pointing to the same old address. If you want it to change, you have to make that change by code also.
If you want that to happen, you can do that in a event setter in the outer class.
First, declare your events like this:
published
...
property OnImageDblClick: TNotifyEvent read fOnImageDblClick write SetOnImageDblClick;
...
procedure TMyClass.SetOnImageDblClick(Value: TNotifyEvent);
begin
FOnImageDblClick := Value;
//pass the new value to the inner object.
if Assigned(Img) then
Img.OnDblClick := Value;
end;
If your Img inner object exists all the time, you don't need the FOnImageDblClick variable, you can write also a getter for the property and take the value directly from the inner object, like this:
function TMyClass.GetOnImageDblClick: TNotifyEvent;
begin
Result := Img.OnDblClick;
end;

Method pointer and regular procedure incompatible

I have an app, which has multiple forms. All these forms have a PopupMenu. I build the menu items programatically, all under a common root menu item. I want ALL the menu items to call the same procedure, and the menu item itself is basically acting as an argument....
I had this working when I just had one form doing this functionality. I now have multiple forms needing to do this. I am moving all my code to a common unit.
Example.
Form A has PopupMenu 1. When clicked, call code in Unit CommonUnit.
Form B has PopupMenu 2. When clicked, call code in unit CommonUnit.
When I need to call my popup from each form, I call my top level procedure (which is in unit CommonUnit), passing the name of the top menu item from each form to the top level procedure in the common unit.
I am adding items to my PopupMenu with with code.
M1 := TMenuItem.Create(TopMenuItem);
M1.Caption := FieldByName('NAME').AsString;
M1.Tag := FieldByName('ID').AsInteger;
M1.OnClick := BrowseCategories1Click;
TopMenuItem.Add(M1);
I am getting an error message when I compile. Specifically, the OnClick line is complaining about
Incompatible types: 'method pointer and regular procedure'.
I have defined BrowseCategories1Click exactly like it was before when I was doing this on a single form. The only difference is that it is now defined in a common unit, rather than as part of a form.
It is defined as
procedure BrowseCategories1Click(Sender: TObject);
begin
//
end;
What is the easiest way to get around this?
Thanks
GS
A little background...
Delphi has 3 procedural types:
Standalone or unit-scoped function/procedure pointers declared like so:
var Func: function(arg1:string):string;
var Proc: procedure(arg1:string);
Method pointers declared like so:
var Func: function(arg1:string):string of object;
var Proc: procedure(arg1:string) of object;
And, since Delphi 2009, anonymous(see below) function/method pointers declared like so:
var Func: reference to function(arg1:string):string;
var Proc: reference to procedure(arg1:string);
Standalone pointers and method pointers are not interchangeable. The reason for this is the implicit Self parameter that is accessible in methods. Delphi's event model relies on method pointers, which is why you can't assign a standalone function to an object's event property.
So your event handlers will have to be defined as part of some class definition, any class definition to appease the compiler.
As TOndrej suggested you can hack around the compiler but if these event handlers are in the same unit then they should already be related anyway so you may as well go ahead and wrap them into a class.
One additional suggestion I have not seen yet is to backtrack a little. Let each form implement its own event handler but have that handler delegate responsibility to a function declared in your new unit.
TForm1.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
TForm2.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
unit CommonUnit
interface
procedure BrowseCategories;
begin
//
end;
This has the added benefit of separating the response to the user's action from the control that triggered the action. You could easily have the event handlers for a toolbar button and a popup menu item delegate to the same function.
Which direction you choose is ultimately up to you but I'd caution you to focus on which option will make maintainability easier in the future rather than which is the most expedient in the present.
Anonymous methods
Anonymous methods are a different beast all together. An anonymous method pointer can point to a standalone function, a method or a unnamed function declared inline. This last function type is where they get the name anonymous from. Anonymous functions/methods have the unique ability to capture variables declared outside of their scope
function DoFunc(Func:TFunc<string>):string
begin
Result := Func('Foo');
end;
// elsewhere
procedure CallDoFunc;
var
MyString: string;
begin
MyString := 'Bar';
DoFunc(function(Arg1:string):string
begin
Result := Arg1 + MyString;
end);
end;
This makes them the most flexible of the procedural pointer types but they also have potentially more overhead. Variable capture consumes additional resources as does inline declarations. The compiler uses a hidden reference counted interface for inline declarations which adds some minor overhead.
You can wrap your procedures into a class. This class might look like this in a separate unit:
unit CommonUnit;
interface
uses
Dialogs;
type
TMenuActions = class
public
class procedure BrowseCategoriesClick(Sender: TObject);
end;
implementation
{ TMenuActions }
class procedure TMenuActions.BrowseCategoriesClick(Sender: TObject);
begin
ShowMessage('BrowseCategoriesClick');
end;
end.
And to assign the action to a menu item in a different unit is enough to use this:
uses
CommonUnit;
procedure TForm1.FormCreate(Sender: TObject);
begin
PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick;
end;
Update:
Updated to use class procedures (instead of object methods) by David's suggestion. For those who want to use the object methods with the need of object instance, follow this version of the post.
This is the difference between a "procedure" and a "procedure of object"
The OnClick is defined as a TNotifyEvent:
type TNotifyEvent = procedure(Sender: TObject) of object;
You cannot assign a procedure to the OnClick as it is the wrong type. It needs to be a procedure of object.
You could choose one of these:
Derive your forms from a common ancestor and declare the method in it so it's available to descendants
Use a global instance of a class (e.g. data module) shared by all forms
Use a procedure as a fake method like this:
procedure MyClick(Self, Sender: TObject);
begin
//...
end;
var
M: TMethod;
begin
M.Data := nil;
M.Code := #MyClick;
MyMenuItem.OnClick := TNotifyEvent(M);
end;
One solution is to place the OnClick method into a TDatamodule.

Delphi: Correct way to store objects fetched from TObjectList

This example is of course simplified, but basically I have a main form that triggers another form (frmSettings) with
function Execute(var aSettings: TSettings):Boolean
TSettings is my own object created in main form for keeping track of the settings.
In this newly opened form (frmSettings) I fetch a TMyObjectList that is a descendant from TObjectList.
It's filled with TMyObj.
I then fill a TListBox with values from that TMyObjectList.
the code:
...
FMyObjectList : TMyObjectList;
property MyObjectList: TMyObjectList read getMyObjectList;
...
function TfrmSettings.getMyObjectList: TMyObjectList ;
begin
If not Assigned(FMyObjectList) then FMyObjectList := TMyObjectList.Create(True)
Result := FMyObjectList;
end;
function TfrmSettings.Execute(var aSettings: TSettings): Boolean;
begin
//Fill myObjectList
FetchObjs(myObjectList);
//Show list to user
FillList(ListBox1, myObjectList);
//Show form
ShowModal;
Result := self.ModalResult = mrOk;
if Result then
begin
// Save the selected object, but how??
// Store only pointer? Lost if list is destroyed.. no good
//Settings.selectedObj := myObjectList.Items[ListBox1.ItemIndex];
// Or store a new object? Have to check if exist already?
If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);
end;
end;
procedure TfrmSettings.FillList(listBox: TListBox; myObjectList: TMyObjectList);
var
i: Integer;
begin
listBox.Clear;
With myObjectList do
begin
for i := 0 to Count - 1 do
begin
//list names to user
listBox.Items.Add(Items[i].Name);
end;
end;
end;
procedure TfrmSettings.FormDestroy(Sender: TObject);
begin
FreeAndNil(FMyObjectList);
end;
Storing just the pointer doesn't seem as a good idea, as triggering the settings form again, recreates the list, and the original object would be lost even if user hits "cancel"
So storing a copy seems better, using assign to get all the properties correct. And first checking if I already have an object.
If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);
Should I move those two lines to a method instead like Settings.AssignSelectedObj(aMyObj:TMyObj)
Does this look correct or am I implementing this the wrong way?
Something more/less needed?
I need some guidelines so I feel more secure that I don't open up for memory leaks and other trouble.
Other than that reviewing the code a bit, the real question is: Is this the correct way to store my SelectedObject in the settings class?
Is this the correct way to store the selected object in the settings?
Probably not. Your settings class should not depend on the form in any way. What if you decide to create and destroy your form dynamically each time the user opens the settings? In this case your settings would hold an invalid object reference.
IMHO it is better to store the object list in the settings together with the index of the selected object. The form should just access the settings, fill the list box and modify the selected object index after the user confirmed with OK.
You are producing a memory leak in your code. You create a TObjectList as a local variable but you never free it. And if you free the local variable, the object references in the listbox will be invalid. You have two options:
Store the object list as a member variable of your form, create in the FromCreate event handler and destroy it in the FormDestroy event handler. You can then safely use object references in your list box.
Store the object list somewhere outside and pass it into the form as a parameter of the Execute method. In this scenario, you can also safely use object references.
I would rename myObjectList to GlobalObjectList, and move it out of the class. It can be declared in the form, but create/free in the initialization/finalization sections. During initialization, after you create the list, populate it from the ini file (or wherever you store it). Now you can access it from anywhere that has your unit in the Uses.
What about the serialization of TSettings? Put your settings in some published properties, then let the RTTI save its content:
type
TSettings = class(TPersistent)
public
function SaveAsText: UTF8String;
end;
function TSettings.SaveAsText: UTF8String;
begin
var
Stream1, Stream2: TMemoryStream;
begin
Stream1 := TMemoryStream.Create;
Stream2 := TMemoryStream.Create;
try
Stream1.WriteComponent(MyComponent);
ObjectBinaryToText(Stream1, Stream2);
SetString(result,PAnsiChar(Stream2.Memory),Stream2.Size);
finally
Stream1.Free;
Stream2.Free;
end;
end;
Then your settings can be stored in a text file or text string.
It's just one solution. But storing settings as text is very handy. We use such an approach in our framework, to store settings via a code-generated user interface. A settings tree is created, from a tree of TPersistent instances.

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