Expanding the functionality of existing example - delphi

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.

Related

Function to recreate a TForm in Delphi

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;

Switching modal forms in Delphi

I have a modal form (A) that shows another modal form (B). B displays a dataset and allows the user to interact with it. My problem is that one action requires that A becomes again the focused form so the user can input certain values without closing B. I have tried A.BringToFront and A.SetFocus and it indeed is shown at front, but the input focus remains in B and any click or the like in A results in the windows "ding" when you click where you should not. The code is some how like
A.ShowModal;
.
.
. inside an event of A:
B.ShowModal();
.
.
. inside an event of B:
someobject.someMethodThatRequiresAFocused;
My guess is that some obscure and strange API call could make A modal again ¿Any ideas?
Regards
When a modal form is shown, all currently visible forms including other modal forms are disabled. As such, it is not possible to switch between multiple modal forms. You need to re-think your UI design so that B does not go back to A for new input. At the very least, you could have B open a new modal form C that prompts the user for just the needed values and gives them to B, and then either B or C can update A with the new values afterwards.
There's no API that toggles modality between windows. In any case the API you're looking for your case is EnableWindow. That's how modality works, windows other than the one that the user should be working with are disabled so that he/she cannot interact with them. This is also the reason for the 'ding' sound, to provide feedback to the user.
So while letting the user work with a window that's been disabled in favor of another modal window is technically easy, handling states may not be straight forward. I present a bare minimum example below for what it would seem to take.
'FormB' first. Lets suppose you pass a reference of 'FormA' in the 'Owner' parameter while 'FormA' is constructing 'FormB'. The below is what the code that should make 'FormA' modal again could look like:
procedure TFormB.BtnMakeFormAModalAgainClick(Sender: TObject);
begin
Enabled := False; // so that 'A' will behave like it's modal
EnableWindow(TFormA(Owner).Handle, True); // so that 'A' could be interacted
TFormA(Owner).SetFocus;
end;
When this code runs, what happens is 'FormA' is enabled and brought to front, and 'FormB' is disabled - will produce a 'ding' when clicked on.
But we're not done yet. Because we have modified the meaning of modality - now we don't want 'FormA' to be closed when the user is done with it. Below is how could the code in 'FormA's unit could look like:
type
TFormA = class(TForm)
BtnShowModalB: TButton;
BtnOk: TButton;
procedure BtnShowModalBClick(Sender: TObject);
procedure BtnOkClick(Sender: TObject);
private
FModalB: TForm;
end;
implementation
uses
unitOfFormB;
{$R *.dfm}
procedure TFormA.BtnShowModalBClick(Sender: TObject);
begin
FModalB := TFormB.Create(Self); // so that FormB can find FormA from the Owner
FModalB.ShowModal;
FModalB.Free;
FModalB := nil; // Need this if we're going to decide if FormB is showing
// by testing against this reference
end;
procedure TFormA.BtnOkClick(Sender: TObject);
begin
if Assigned(FModalB) then begin // is FormB the actual modal form?
EnableWindow(Handle, False); // disable this form so it would 'ding'
FModalB.Enabled := True; // enable FormB, so user can interact with it
FModalB.SetFocus;
ModalResult := mrNone; // don't close, FormB is the first one to be closed
end else
ModalResult := mrOk;
end;
I'm nearly positive that this example is not complete, but here's the API that you're looking for.

How can I Prevent Shortcuts from Colliding/Interacting in Delphi?

I use the standard Cut, Copy, Paste actions on my Main Menu. They have the shortcuts Ctrl-X, Ctrl-C and Ctrl-V.
When I open a modal form, e.g. FindFilesForm.ShowModal, then all the shortcuts work from the form.
But when I open a non-modal form, e.g. FindFilesForm.Show, then the shortcuts do not work.
I would think that those actions should work if the FindFilesForm is the active form. It's modality should have nothing to do with it, or am I wrong in my thinking?
Never-the-less, how can I get the shortcuts to work on a non-modal form?
After Cary's response, I researched it further. It is not a problem with certain controls, e.g. TMemo or TEdit.
But it is for some others. Specifically, the ones where it happens include:
the text in a TComboBox
the text in a TFindDialog
a TElTreeInplaceEdit control, part of LMD's ElPack
I'll see if there are others and add them to the list.
These are all on important Non-Modal forms in my program.
So I still need a solution.
Okay. I really need help with this. So this becomes the first question I am putting a bounty on.
My discussion with Cary that takes place through his answer and the comments there describe my problem in more detail.
And as I mentioned in one of those comments, a related problem seems to be discussed here.
What I need is a solution or a workaround, that will allow the Ctrl-X, Ctrl-C and Ctrl-V to always work in a TComboBox and TFindDialog in a Non-Modal window. If those two get solved, I'm sure my TElTreeInplaceEdit will work as well.
It takes only a couple of minutes to set up an simple test program as Cary describes. Hopefully someone will be able to solve this.
Just be wary that there seems to be something that allows it to work sometimes but not work other times. If I can isolate that in more detail, I'll report it here.
Thanks for any help you can offer me.
Mghie worked very hard to find a solution, and his OnExecute handler combined with his ActionListUpdate handler do the trick. So for his effort, I'm giving him the accepted solution and the bounty points.
But his actionlist update handler is not simple and you need to specify in it all the cases you want to handle. Let's say there's also Ctrl+A for select all or Ctrl-Y for undo you might want. A general procedure would be better.
So if you do come across this question in your search for the answer, try first the answer I supplied that adds an IsShortcut handler. It worked for me and should handle every case and does not need the OnExecute handlers, so is much simpler. Peter Below wrote that code and Uwe Molzhan gets finders fee.
Thanks Cary, mghie, Uwe and Peter for helping me solve this. Couldn't have done it without you. (Maybe I could have, but it might have taken me 6 months.)
OK, first thing first: This has nothing to do with modal or non-modal forms, it is a limitation of the way the Delphi action components work (if you want to call it that).
Let me prove this by a simple example: Create a new application with a new form, drop a TMemo and a TComboBox onto it, and run the application. Both controls will have the system-provided context menu with the edit commands, and will correctly react on them. They will do the same for the menu shortcuts, with the exception of Ctrl + A which isn't supported for the combo box.
Now add a TActionList component with the three standard actions for Cut, Copy and Paste. Things will still work, no changes in behaviour.
Now add a main menu, and add the Edit Menu from the template. Delete all commands but those for Cut, Copy and Paste. Set the corresponding action components for the menu items, and run the application. Observe how the combo box still has the context menu and the commands there still work, but that the shortcuts do no longer work.
The problem is that the standard edit actions have been designed to work with TCustomEdit controls only. Have a look at the TEditAction.HandlesTarget() method in StdActns.pas. Since edit controls in combo boxes, inplace editors in tree controls or edit controls in native dialogs are not caught by this they will not be handled. The menu commands will always be disabled when one of those controls has the focus. As for the shortcuts working only some of the time - this depends on whether the VCL does at some point map the shortcuts to action commands or not. If it doesn't, then they will finally reach the native window procedure and initiate the edit command. In this case the shortcuts will still work. I assume that for modal dialogs the action handling is suspended, so the behaviour is different between modal and non-modal dialogs.
To work around this you can provide handlers for OnExecute of these standard actions. For example for the Paste command:
procedure TMainForm.EditPaste1Execute(Sender: TObject);
var
FocusWnd: HWND;
begin
FocusWnd := GetFocus;
if IsWindow(FocusWnd) then
SendMessage(FocusWnd, WM_PASTE, 0, 0);
end;
and similar handlers for the Cut command (WM_CUT) and the Copy command (WM_COPY). Doing this in the little demo app makes things work again for the combo box. You should try in your application, but I assume this will help. It's a harder task to correctly enable and disable the main menu commands for all native edit controls. Maybe you could send the EM_GETSEL message to check whether the focused edit control has a selection.
Edit:
More info why the behaviour is different between combo boxes on modal vs. non-modal dialogs (analysis done on Delphi 2009): The interesting code is in TWinControl.IsMenuKey() - it tries to find an action component in one of the action lists of the parent form of the focused control which handles the shortcut. If that fails it sends a CM_APPKEYDOWN message, which ultimately leads to the same check being performed with the action lists of the application's main form. But here's the thing: This will be done only if the window handle of the application's main form is enabled (see TApplication.IsShortCut() code). Now calling ShowModal() on a form will disable all other forms, so unless the modal dialog contains itself an action with the same shortcut the native shortcut handling will work.
Edit:
I could reproduce the problem - the key is to somehow get the edit actions become disabled. In retrospect this is obvious, the Enabled property of the actions needs of course to be updated too.
Please try with this additional event handler:
procedure TForm1.ActionList1Update(Action: TBasicAction; var Handled: Boolean);
var
IsEditCtrl, HasSelection, IsReadOnly: boolean;
FocusCtrl: TWinControl;
FocusWnd: HWND;
WndClassName: string;
SelStart, SelEnd: integer;
MsgRes: LRESULT;
begin
if (Action = EditCut1) or (Action = EditCopy1) or (Action = EditPaste1) then
begin
IsEditCtrl := False;
HasSelection := False;
IsReadOnly := False;
FocusCtrl := Screen.ActiveControl;
if (FocusCtrl <> nil) and (FocusCtrl is TCustomEdit) then begin
IsEditCtrl := True;
HasSelection := TCustomEdit(FocusCtrl).SelLength > 0;
IsReadOnly := TCustomEdit(FocusCtrl).ReadOnly;
end else begin
FocusWnd := GetFocus;
if IsWindow(FocusWnd) then begin
SetLength(WndClassName, 64);
GetClassName(FocusWnd, PChar(WndClassName), 64);
WndClassName := PChar(WndClassName);
if AnsiCompareText(WndClassName, 'EDIT') = 0 then begin
IsEditCtrl := True;
SelStart := 0;
SelEnd := 0;
MsgRes := SendMessage(FocusWnd, EM_GETSEL, WPARAM(#SelStart),
LPARAM(#SelEnd));
HasSelection := (MsgRes <> 0) and (SelEnd > SelStart);
end;
end;
end;
EditCut1.Enabled := IsEditCtrl and HasSelection and not IsReadOnly;
EditCopy1.Enabled := IsEditCtrl and HasSelection;
// don't hit the clipboard three times
if Action = EditPaste1 then begin
EditPaste1.Enabled := IsEditCtrl and not IsReadOnly
and Clipboard.HasFormat(CF_TEXT);
end;
Handled := TRUE;
end;
end;
I didn't check for the native edit control being read-only, this could probably be done by adding this:
IsReadOnly := GetWindowLong(FocusWnd, GWL_STYLE) and ES_READONLY <> 0;
Note: I've given mghie the answer as he did a lot of work and his answer is correct, but I have implemented a simpler solution that I added as an answer myself
I posted a link to this question on my blog, and got a suggestion from Uwe Molzhan who is not on StackOverflow. Uwe used to run DelphiPool. He pointed me to this thread at borland.public.delphi.objectpascal:
Action List (mis)behavior.
Tom Alexander who asked the original question in this thread even said:
This behavior occurs usually, but not
all the time. Sometimes after a series
of the above errors, the behavior
starts acting as I would expect.
which is exactly the strange behaviour I've been having that has made this problem near to impossible to track down.
Peter Below responded in that thread that if there are colliding shortcuts, you have to take steps to make sure the active control gets first crack at the shortcut.
Taking his code (which was written for a frames problem) and I just had to modify “ctrl is TCustomFrame” to “ctrl is TControl” and it works perfect. So here is what was needed:
public
Function IsShortcut( var Message: TWMKey): Boolean; override;
Function TMyform.IsShortcut( var Message: TWMKey): Boolean;
Var
ctrl: TWinControl;
comp: TComponent;
i: Integer;
Begin
ctrl := ActiveControl;
If ctrl <> Nil Then Begin
Repeat
ctrl := ctrl.Parent
Until (ctrl = nil) or (ctrl Is TControl);
If ctrl <> nil Then Begin
For i:= 0 To ctrl.componentcount-1 Do Begin
comp:= ctrl.Components[i];
If comp Is TCustomActionList Then Begin
result := TCustomActionList(comp).IsShortcut( message );
If result Then
Exit;
End;
End;
End;
End;
// inherited; { Originally I had this, but it caused multiple executions }
End;
So far this seems to work in all cases for me.
The ironic thing is that it didn't work for Tom Alexander, the original question asker. What he did instead was add a procedure to the FrameEnter event that set the focus to the appropriate grid for the frame. That might imply yet another alternative solution to my question, but I have no need to explore that since Peter's solution works for me.
Also note that Peter includes in his answer an excellent summary of the complex steps of key handling that is worth knowing.
But I do want to now check mghie's edit on his answer and see if that is also a solution.
I created a very simple example with two forms in Delphi 2009 (Update 3 and Update 4 installed) running on Vista 64-bit. The second form, Form2 is displayed non-modally (Form2.Show;). I have a TMemo on Form2. Ctrl-X, Ctrl-V, and Ctrl-C work just fine.
This was before I placed a TMainMenu on Form2.
So, I placed a TMainMenu on the form, and added a TActionList. I create an Edit menu items, and added Copy, Cut, Paste submenu items. I hooked these up to the standard actions EditCopy, EditCut, and EditPaste. Still, everything works fine as before. I can either use the menu items, or the Ctrl-C, Ctrl-X, and Ctrl-V key combinations.
There must be something else going on here.

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