How to access to parent form in Delphi - forms

I'm writing my own component which inherited from TButton. I need to make some manipulation with parent form where my new component will be placed.
So, How to access to parent form from my own component code?
Code example (MyComponentCode.pas):
ButtonParent.Canvas.Pen.Color := RGB(255,255,255); // where "ButtonParent" have to be a parent form
Help me to resolve this problem. Thank you.

To access the parent TForm that your component resides on, even if your component is actually on another container control (like a TPanel or TFrame), use the GetParentForm() function in the Vcl.Forms unit:
uses
..., Forms;
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
//...
end;

The parent is the control that holds the control.
If you drop a control on a panel, then the parent will be the panel.
The owner of a control will usually be the form that holds it, but this is not always the case. If you work with Frames, than the frame will own the controls inside it.
The way to get to the form that owns your control is to keep going up the tree until you find a real form.
You can call VCL.Forms.GetParentForm, which looks like this:
function GetParentForm(Control: TControl; TopForm: Boolean = True): TCustomForm;
begin
while (TopForm or not (Control is TCustomForm)) and (Control.Parent <> nil) do
Control := Control.Parent;
if Control is TCustomForm then
Result := TCustomForm(Control) else
Result := nil;
end;
Or if you want to get there through the owner you can do:
function GetOwningForm(Control: TComponent): TForm;
var
LOwner: TComponent;
begin
LOwner:= Control.Owner;
while Assigned(LOwner) and not(LOwner is TCustomForm) do begin
LOwner:= LOwner.Owner;
end; {while}
Result:= LOwner;
end;
It's important to grok the difference between the parent and the owner, see:
http://delphi.about.com/od/objectpascalide/a/owner_parent.htm
Of course you can use the same trick with the parent property. If you go up the tree long enough (almost) every control1 will have the form as its parent.
1) some controls have no parent.

Related

how to initialize a form created with designer in delphi?

how to initialize a form if for example i have one form created with designer and i want a second form that would be seperate but work as my full screen. the second form also has parameters that i want to write to but i get access violation.
Im currently trying to access these second form variables in first form's formcreate method. but like i said i get access violation so im guessing i need to initialize the second form to access variables in that form but i dont know how to do it, and where is the appriopriate way to do it
You're doing it wrong. You should not have one form's initialization be dependent on another form's existence. You should do one of three things:
1) (Preferred) Move the fields out from TForm2 to a non-form CLASS defined in a UNIT that you include in both Form1.PAS and Form2.PAS, and that you then create from within your TForm1.FormCreate
2) (Only if you can't do 1) Delay accessing Form2's fields until Form1's OnActivate, and ensure that OnActivate is only called once:
PROCEDURE TForm1.OnActivate(Sender : TObject);
BEGIN
OnActivate:=NIL;
<Access Form2.Field>
END;
3) (Only if 1 or 2 is undoable) Instantiate Form2 wihtin Form1's FormCreate (and remove it from auto-created forms):
PROCEDURE TForm1.FormCreate(Sender : TObject);
BEGIN
Application.CreateForm(Form2,TForm2); // Or Form2:=TForm2.Create(Application);
<Access Form2.Field>
END;
Of these three options, 1) is absolutely preferable...

Delphi get (and destroy) an instance of a form

In an application, that uses a TPanel on a "main form", to display other forms, I need to be able not only to display those forms on the TPanel, but also to close and destroy them using controls (buttons) on the main form.
The goal is following - many buttons, each one displaying a specific form on one panel, on the main form. Then, one button, that kills any possible form, that is currently being displayed (= embedded on the panel). Plus, the same closing/killing action should be called every time, when some of the "opening" buttons are triggered, so if some form is being displayed on the panel, it should be replaced by the new form.
To display a form inside the TPanel, I use something like this:
procedure TMainForm.Button1Click(Sender: TObject);
begin
if not assigned(form4) then
form4:= TForm4.Create(Panel2);
form4.Parent:= Panel2;
form4.Show;
end;
Now, to close the window, using another button on the main form, I tried various methods, icluding a CloseWindow method, using handles or pointers etc. The most promissing way, was this:
procedure TMainForm.Button2Click(Sender: TObject);
begin
Panel2.controls[0].Free;
end;
It actually closes the form, but since FreeAndNil is not used, repeated click on the Button1, leads to a nasty series of exceptions, because the form (form4 in this case) has been freed, but the reference to it has not, so the assigned() method returns true and then I try to assign a value to something, that no longer exists with form4.Parent:= Panel2;. An onClose Action:= caFree on the embedded form, does not help either, because the onClose action is not being triggered at all... Placing FreeAndNil(Form4) in onDestroy event of the Form4, also leads to a series of exceptions starting with "Invalid pointer operation".
Using
procedure TMainForm.Button2Click(Sender: TObject);
begin
FreeAndNil(Panel2.controls[0]);
end;
leads to a following error: [dcc32 Error] Unit3.pas(47): E2197 Constant object cannot be passed as var parameter
So what is the correct way to get an instance of the embedded form, to be able to use FreeAndNil(...the form...)? I cannot address it by a name, mainly because I don't know what form is currently being displayed. I need to be able to find an instance of the form based on fact, that it belongs to the TPanel on the main form, and then completely destroy it, so that a second click on the Button1, will display it again, and another click on the Button2, will close and destroy it again.
The key to the solution is Vcl.Forms.TFormClass. From the documentation:
TFormClass is the metaclass for TForm. Its value is the class
reference for TForm or for one of its descendants.
And use it as follows:
First, ditch the FormN global variables from all units that define forms that you will be showing in the panel of the main form. You will not need them and deleting them will prevent you from doing mistakes.
Secondly, in the main form, add a private declaration, CurrentForm: TForm and a private procedure ShowForm
...
private
CurrentForm: TForm;
procedure ShowForm(aFormClass: TFormClass);
...
Because of the aFormClass: TFormClass argument, you can pass in any form type.
Write event handlers for the buttons that should create and show the forms in the panel, similar to this:
procedure TForm25.ShowFormAClick(Sender: TObject);
begin
ShowForm(TForm26);
end;
And write the ShowForm() method:
procedure TForm25.ShowForm(aFormClass: TFormClass);
begin
CurrentForm.Free;
CurrentForm := aFormClass.Create(self);
CurrentForm.Parent := Panel1;
CurrentForm.Show;
end;
Finally also write the event handler for the button that should hide whatever form is currently displayed:
procedure TForm25.ShowNothingClick(Sender: TObject);
begin
FreeAndNil(CurrentForm);
end;
What you're doing wrong
You're completely misunderstanding Delphi's default global form variables. E.g. in this case Form4: TForm4;. (And yet again, I lament the fact that Delphi clings to this horrendous design shortcut.)
Just because Delphi happens to generate this for you doesn't give it any special meaning. You cannot assume it in any way binds to a specific instance of TForm4. And you most certainly shouldn't assume its Assigned status gives any indication how many instances of the form exist. Indeed, as you've noticed, even when Assigned(Form4) = True it's possible the instance that Form4 was referring to has already been destroyed.
To gain a better understanding, experiment with the following:
{Add these to your main form}
FForm4a: TForm4;
FForm4b: TForm4;
FHelloWorld: TForm4;
{Try the following in a button click event
You should see 3 instances of TForm4.}
FForm4a := TForm4(Self);
FForm4b := TForm4(Self);
FHelloWorld := TForm4(Self); {You might not have realised, but the
identifier can be completely different
to the class name.}
FForm4a.Show();
FForm4b.Show();
FHelloWorld.Show();
{In another try}
FreeAndNil(FHelloWorld);
if not Assigned(FHelloWorld) then
ShowMessage('Code explicitly ensured the reference to the form was cleared.');
FForm4b.Free;
if Assigned(FForm4b) then
ShowMessage('If you do not clear the reference, the form variable *remains* assigned.');
{Finally close all TForm4 instances through the UI
(if your form settings allow it), and call the following.}
if Assigned(FForm4a) then
ShowMessage('Form4a is assigned.');
if Assigned(FForm4b) then
ShowMessage('Form4b is assigned.');
if Assigned(FHelloWorld) then
ShowMessage('HelloWorld is assigned.');
So how can you solve your problem?
For a start if you want to know about forms that are on Panel2, check Panel2! Don't waste time checking global variables that have nothing to do with what's on Panel2.
function TMainForm.DoesForm4Exist(): Boolean;
begin
Result := True;
for I := 0 to Panel2.ControlCount-1 do
begin
if (Panel2.Controls[I] is TForm4) then Exit;
end;
Result := False;
end;
You now have a reliable way to check if Panel2 currently has a TForm4 instance. And this should set you on the right path.
Improving the solution
You can make the above code generic so you can reuse it for your other forms. At the same time, you can return a reference to the form, so you interact with it programatically. E.g. to destroy it:
function TMainForm.FindForm(AFormClass: TFormClass): TForm;
begin
Result := nil;
for I := 0 to Panel2.ControlCount-1 do
begin
if (Panel2.Controls[I] is AFormClass) then
begin
Result := TForm(Panel2.Controls[I])
Exit;
end;
end;
end;
{Example using the code to find and destroy a TForm4 instance}
LForm := FindForm(TForm4);
if Assigned(LForm) then LForm.Free;
You can similarly make the form creation generic as per Tom's answer.
Your main problem is, you're trying to solve a problem that doesn't exist. Mainly, you don't need to keep track of the form that is potentially being displayed in the panel and hence you don't have to use FreeAndNil at all.
That is because you can ask the panel anytime what it has got, and since you know what form you are attempting to display when you click a button, you can specifically ask the panel if it has got that kind of specific form.
procedure DisplayFormInPanel(Panel: TPanel; FormClass: TFormClass);
begin
if (Panel.ControlCount > 0) and not (Panel.Controls[0] is FormClass) then
Panel.Controls[0].Free;
if Panel.ControlCount = 0 then
begin
with FormClass.Create(Panel) do begin
Parent := Panel;
Show;
end;
end;
end;
The above procedure gets rid of any possible form that is not of the kind you want to display. If there's already a form that is of the kind that is going to be displayed, then nothing happens - the form stays in the panel.
Call it like:
procedure TForm1.Button1Click(Sender: TObject);
begin
DisplayFormInPanel(Panel2, TForm4);
end;
And your button click handler that closes any form could be as simple as
if Panel.ControlCount > 0 then
Panel.Controls[0].Free;
Regarding the DCC error, there is no point in pursuing that because the reference in the controls array is not the same as the "form4" reference. Even if you could nil that reference, it wouldn't effect "form4" and hence would not help with testing for Assigned. And the reference in the controls array is going to be removed from the controls array as soon as the form is freed anyway.

How to call another form using a panel in the main form?

I am coding in Delphi 10. I have two forms: FormPrincipal, which is the main form, and Formbanco the one I want to call.
In FormPrincipal I put a panel PanelCorpo and I want to call Formbanco and show it in the position of this panel.
I have tried two methods, but both did not work. See below:
1st) FormPrincpal calling Formbanco using Showmodal:
// TActionlist OnExecute event
procedure TFormPrincipal.AbreFormBancoExecute(Sender: TObject);
begin
try
Application.CreateForm(Tformbanco,Formbanco);
Formbanco.Parent := PanelCorpo;
Formbanco.Align := alclient;
Formbanco.Showmodal;
finally
Freeandnil(formbanco);
end;
end;
The behavior was: it opened the called form Formbanco properly, but frozen. Both forms did not allow to focus!
2nd) FormPrincpal calling Formbanco using Show:
// TActionlist OnExecute event
procedure TFormPrincipal.AbreFormBancoExecute(Sender: TObject);
begin
try
Application.CreateForm(Tformbanco,Formbanco);
Formbanco.Parent := PanelCorpo;
Formbanco.Align := alclient;
Formbanco.Show;
finally
Freeandnil(formbanco);
end;
end;
The behaviour was: it blinks very quickly the Formbanco, almost not visible, and continues in FormPrincipal. I can't access Formbanco!
I do appreciate help on this.
A modal form can't be a child. So the second attempt using Show is better. The mistake there is to destroy the form. Remember that Show is asynchronous, so you destroy the form as soon as you create it. Don't do that. You will need to destroy it somewhere else, in response to another event. You will know what that should be.
The function should look like this:
procedure TFormPrincipal.AbreFormBancoExecute(Sender: TObject);
begin
Formbanco := Tformbanco.Create(Self);
Formbanco.Parent := PanelCorpo;
Formbanco.Align := alclient;
Formbanco.BorderIcons := [];
Formbanco.BorderStyle := bsNone;
Formbanco.Show;
end;
If you use TForm as a panel, don't do it. Create TFrame and include it as every component on your FormPrincipal.
If you want to include the content of a form on an other (inside a panel, a tabcontrol, ...) you can only do if you put a tpanel (or tlayer) as top level parent on the second form. When you want to include it on an other form change it's Parent property (and it's alignment if necessary). You don't have to show your second form in this case (but of course create it) : the panel/layout is on first one and showed on it.

Create secondary form before main form while keeping FormMain as main form?

In the Delphi 10.1 Berlin IDE, with a VCL Form application project, in the Project Options dialog, I have these settings:
Now I want the formOptions Form to be created BEFORE the FormMain Form while keeping the FormMain Form as the Main Form. The reason is: The main form could load data in its FormCreate procedure (where formOptions still has not been created), but to process these data it needs the settings from the formOptions form.
But as soon as I drag the formOptions item to the top of the list, also the main form combobox above changes to formOptions!
So how can I make the formOptions Form to be created BEFORE the FormMain Form while keeping the FormMain Form as the Main Form?
The first TForm that is created with Application.CreateForm() becomes the VCL's Application.MainForm, and cannot be changed. So do whatever initializations you need to do before CreateForm() assigns the MainForm.
You have two options:
Remove formOptions from the auto-create list (thus removing the generated CreateForm() call for it), and then create it manually yourself in code. CreateForm() does not assign the MainForm until it is fully created, so you can actually handle this in one of two different ways:
a. directly in the project's .dpr file :
Application.Initialize;
Application.MainFormOnTaskbar := True;
formOptions := TFormOptions.Create(Application);
Application.CreateForm(TFormMain, FormMain);
Application.Run;
b. in the MainForm's OnCreate event:
procedure TFormMain.FormCreate(Sender: TObject);
begin
formOptions := TFormOptions.Create(Application); // or even Self
end;
move your shared settings to a TDataModule (or even a standalone class) instead, and then (auto-)create that object before creating either of the Forms. FormMain and formOptions can then retrieve their settings from that object when needed:
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TSettingsDM, SettingsDM);
Application.CreateForm(TFormMain, FormMain);
Application.CreateForm(TFormOptions, formOptions);
Application.Run;

Delphi: positioning a form in a dll

I am creating a form in a dll. No packages. The form in the dll is called by using the exported procedure:
procedure ShowAbout(const AppHandle: THandle); stdcall;
var
aHandle: THandle;
form: TfrmAbout; / my form in some other unit in the dll
begin
aHandle:= Application.Handle;
Application.Handle:= AppHandle;
form :=TfrmAbout.Create(Application);
form.ShowModal;
form.Free;
Application.Handle:= aHandle;
end;
The form displays well and there are no problems. Now, the only thing I would like it to do is to behave positioning as poMainFormCenter (I want it to display always over the main form (the form that is calling the dll).
I have tried using form :=TfrmAbout.Create(Application.MainForm); etc but no luck.
Any tricks which would help here?
The VCL Position mechanism relies on the other forms in the application all running with the same version of the VCL. This is clearly not the case here and you will have to position the form manually.
Find out the position of the main form by calling GetWindowRect() passing the main form handle. Then you need to work out where your form needs to go to be in the center of that form.
procedure PositionForm(Form: TForm; MainWindow: HWND);
var
MainBounds: TRect;
MainWidth, MainHeight: Integer;
begin
if GetWindowRect(MainWindow, MainBounds) then
begin
MainWidth := MainBounds.Right-MainBounds.Left;
MainHeight := MainBounds.Bottom-MainBounds.Top;
Form.Left := MainBounds.Left + (MainWidth - Form.Width) div 2;
Form.Top := MainBounds.Top + (MainHeight - Form.Height) div 2
end;
By the way, the handle you are passing is an HWND rather than a THandle. You should change you code accordingly. It won't change behaviour, but it is logically correct to do so.
Since you don't use pacakges, your EXE and your DLL both have a seperate TApplication instance. TApplication.MainForm in your EXE is not seen in your DLL. Changeing TApplication.Handle does not make the MainForm change. Find other ways to position the form right, but better yet: Use packages, you will run into more problems if you don't.
Have you tried setting form.ParentWindow to the handle of the parent window? You should pass it as a param to ShowAbout, or you could dig it up from Application object (something like Application.ActiveForm) but I'm not sure it would work.
Calling TfrmAbout.Create(Application.MainForm) just specifies that Application.MainForm is responsible for destruction of the form, it should have nothing to do with window hierarchy, also I'm not sure you should be using auto destruction if you create form in a separate dll.