Please explain the difference between:
ChildForm := TForm.CreateParented(AOwner)
ChildForm := TForm.CreateParentedControl(AOwner)
ChildForm := TForm.Create(AOwner);
ChildForm.ParentWindow := AOwner.Handle
This example may be complicated and convoluted, I'd really just like an overview of when people use the different kinds of Create methods for forms.
Delphi 7 help tells me that I should use CreateParented(AOwner.Handle) and ParentWindow := AOwner.handle with non-VCL controls or across DLL's. Until yesterday I just set Parent := AOwner, and I have absolutely no idea why this stopped working.
(Maybe I just need to reboot my computer)
We have Components. They are visible or invisible items on a form or a datamodule. Each component can have an owner that is responsible for the eventual destruction. If there is no owner, you must take care of the destruction yourself.
We have Controls, which are components that are visible. They also have a Parent which contains the control. For example a Panel is the parent of a button on that panel.
We also have WinControls which are controls that link to windows objects. They also have a handle of the parent window.
So:
TMyControl.CreateParented
constructor CreateParented(ParentWindow: HWnd);
This is used to create a control from which the parent window is provided by an handle.
It creates the control without owner and sets the parentwindow to ParentWindow.
TMyControl.CreateParentedControl
class function CreateParentedControl(ParentWindow: HWND): TWinControl;
Creates the control, without owner, sets the parentwindow to ParentWindow and returns
it.
TMyControl.Create(AOwner: TComponent)
Creates a control with owner set to AOwner.
TMyControl.ParentWindow := AOwner.Handle;
Sets the parentwindow (handle) to the handle of AOwner.
Related
How do I make sure a dynamic control is destroyed along with it's parent form?
So, from the main form, I create a button for a secondary form, then I display the secondary form with the button on it.
Now, I want to make sure the created button is destroyed together with the secondary form.
Would it be enough to make the Parent of the Button, the secondary form? Would that do it?
I am using a custom descendant of the TButton Class - TMyButton. So in my constructor I have the next code:
constructor TMyButton.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.OnClick := Self.MyButtonClick;
Self.Parent:=TWinControl(AOwner);
self.Visible := true;
end;
Is this going to be ok?
It works for me, it throws no errors, but I want to make sure that the said button is destroyed together with the form on which it is placed.
The MyButton will be placed on a secondary form, let's say 'Form2'
So there will be a code like:
var
bt:TMyButton;
begin
bt:=TMyButton.Create(Form2);
bt.Parent:=Form2;
...
form2.Show;
end;
First of all: setting a control's parent in its constructor is wrong! For a control created in design time, the IDE is and will be responsible for setting a parent. For a control created in runtime, the creating code should be responsible for setting a parent.
How do I make sure a dynamic control is destroyed along with it's parent form?
A control which has a Parent will automatically be destroyed when that immediate Parent is destroyed. Furthermore, a control which has an Owner will automatically be destroyed when that Owner is destroyed. It the Owner is in the Parent chain of the control then everything is fine. If the Owner is not in the Parent chain of the control, then you have a design problem which could result in unwanted destruction.
Your current constructor is completely unnecessary. Casting the Owner (which could be nil!) to a TWinControl without checks is wrong. But as said, setting the Parent should not be there. Visible is true by default, and assigning the OnClick event will prevent it for further use. Instead, override Click.
Regarding your comment, the calling code would become:
procedure TForm1.Button1Click(Sender: TObject);
var
Button: TButton;
begin
Button := TButton.Create(Form2);
Button.OnClick := Form2.ButtonClick;
Button.SetBounds(10, 10, 75, 25);
Button.Parent := Form2;
end;
But this leaves you with no reference to the button. Instead add the variable to a higher scope.
With this code you should not have problems.
You're making both the Owner and the Parent is the child form. So when released form. This frees the objects it contains.
I will explain the difference between Parent and Owner:
When assigning the Parent, you are indicating "visual container". Is the object that show ... a Form a Panel, a Frame.
Moreover, the Owner is the owner. The Object owner is responsibility to release all objects "slaves".
Normally both the Parent and the Owner is the same, because we design interfaces automatically and put the components visually.
When you want to create visual components at runtime must be controlled well who is the Parent, and whom the Owner.
Different is if your in the Owner will set Nil:
Obj := TObject.Create(nil);
There if you have to indicate when you should release it. It's your responsibility.
EDIT:
You can use the TButton. Something like this:
procedure TForm1.CreateButton;
var obj: TButton;
begin
obj := TButton.Create(Form2);
obj.Parent := Form2;
Obj.OnClick := MyEventOnClick;
end;
EDIT 2:
Zarko Gajic, in About.com has an excellent article is worth reading.
This can help supplement what you have offered. You can view the article in http://delphi.about.com/od/objectpascalide/a/owner_parent.htm
The short answer is: Yes, your button will be destroyed along with the form.
But it would be useful if you know how to verify this for yourself.
Write the following code:
(NOTE: Don't forget override where you declare the method.)
destructor TMyButton.Destroy;
begin
inherited Destroy;
end;
Now put a breakpoint on the line inherited Destroy;. Run your application in the debugger, and ensure the code stops at the breakpoint. I.e.
Open your child form.
Destroy your child form. (Emphasis on Destroy, because if CloseAction is caHide, simply closing your form won't be sufficient for the test.)
Verify that you reach the breakpoint.
Press F9 to continue running.
Also verify that you reach the breakpoint only once per button. (Otherwise there would be a double-destroy bug that could lead to access violations and unpredictable behaviour.)
You can also enable Debug DCU's in your compiler options. Then when you hit the breakpoint examine the call stack Ctrl+Alt+S to see where in VCL code your button is being destroyed from.
My Delphi XE7 FireMonkey project is growing controls and naturally I've moved to using frames. Where I've used frames in the VCL there have been situations where I've simply chosen to host one (complex) VCL form inside another instead, creating and displaying it in the form's OnShow and setting it client-aligned (the benefit of this is that you don't get issues with dangling inherited controls when you edit the frame).
With FireMonkey though, things have changed slightly and my attempt to get a child form client aligned inside another is stumbling. I came across this very useful SO link which shows how to host a FireMonkey form inside a VCL form so I built on this with my code as follows:
procedure THostForm.FormCreate(Sender: TObject);
begin
FForm := TChildForm.Create( Self );
FForm.BorderIcons := [];
FForm.BorderStyle := TFmxFormBorderStyle.None;
FForm.Visible := True;
FForm.Parent := Self;
ResizeForm;
end;
procedure THostForm.FormResize(Sender: TObject);
begin
inherited;
ResizeForm;
end;
procedure THostForm.ResizeForm;
begin
if Assigned(FForm) then
FForm.SetBounds( Round(ClientRect.Left), Round(ClientRect.Top), Round(ClientWidth), Round(ClientHeight));
end;
This produces a child form which changes size with the host form, but remains at the top left of the screen. I've tried various position options in the ResizeForm routine too. It seems to me that a form might not be able to be the parent of another because TForm is not IAligneableControl whereas TFrame is. So, I tried 'docking' my child form to a TRectangle client aligned in the host form and this behaves the same way.
Has anyone examined this?
* SOLUTION DETAIL AS SUGGESTED BY MARCO BELOW *
Marco's solution is very neat and reduces the 'hosting' to just two lines of code. You do need to ensure that your child (hosted) form has everything inside another client aligned control - Marco suggested using a TLayout, but I already had a TPanel that I am using for a background so I had no modifications to the child form at all. So, to host this child form TChildForm inside a THostForm simply do:
procedure THostForm.FormCreate(Sender: TObject);
begin
FForm := TChildForm.Create( Self );
FForm.Panel1.Parent := Self;
end;
Job done. Thanks Marco.
Mixing forms and controls in FireMonkey is not such a good idea as it is the VCL, because in the VCL controls and form are all TWinControl descendant with their own Windows handle, while in FireMoneky the form is associuated with an operating system object while the controls are not.
The address scenario, I've used a different solution. Created a form with a client-aligned, useless TLayout with all of the controls inside it. At runtime, create this form and parent the Layout to the new container (for example a tab in a multi tab control).
I've used this a few times, never found big issues with it, and a nice way to dynamically crate tab pages keeping the visual development model.
How to make my non-modal forms to always be on top of my main form?
I have tried:
procedure TForm3.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.WndParent := Application.MainForm.Handle;
end;
Which seems to work fine. Is that correct way?
This is the Win32 concept of window ownership. An owned window always appears on top of its owner. The owner is specified in the call to CreateWindow and can then not be modified.
In the VCL you specify the owner by setting WndParent in CreateParams, and the framework then passes that on to CreateWindow. The VCL does this for you but in older versions the owner handling is, well, somewhat flaky. Modern versions are better and allow more control through the PopupMode and PopupParent properties.
Your code will therefore have the effect that you desire.
I would like to have a seperate form that shows "along" with my main form, so it does not overlap the main form.
Here's an example:
Notice how the main program, overlaps the log? I can't figure out how to do that in Delphi.
Thanks!
The answers to this question lie in the very useful Window Features MSDN topic.
The pertinent information is:
An overlapped or pop-up window can be
owned by another overlapped or pop-up
window. Being owned places several
constraints on a window.
An owned window is always above its owner in the z-order.
The system automatically destroys an owned window when its owner is
destroyed.
An owned window is hidden when its owner is minimized.
The main form in your app is the owner (in Windows terminology rather than Delphi terminology) of the other popup windows. The first bullet point above implies that the owned windows always appear above the main form (the owner).
Try creating an app with 3 forms and show them all. The .dpr would look like this:
program OwnedWindows;
uses
Forms,
Main in 'Main.pas' {MainForm},
Popup1 in 'Popup1.pas' {PopupForm1},
Popup2 in 'Popup2.pas' {PopupForm2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, Main);
Application.CreateForm(TPopupForm1, PopupForm1);
Application.CreateForm(TPopupForm2, PopupForm2);
PopupForm1.Show;
PopupForm2.Show;
Application.Run;
end.
You will see that the main form is always underneath the other two forms, but these other owned forms can be above or below each other. When you minimize the main form they all disappear.
You could if you want make all of your forms top-level unowned windows:
procedure TPopupForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := 0;
end;
And like wise for TPopupForm2 in my example. This would result in all 3 windows having taskbar buttons.
One other approach is to revert to the pre-Vista way of things and make the Application's hidden window be the top-level owner window. You do this by making sure that Application.MainFormOnTaskbar is False. Skip all the CreateParams code and you'll now have a single window on the taskbar and any of your windows can be above any other because the top-level owner window is the hidden window Application.Handle. Of course the downside is that you lose your Aero Peek.
So, I guess what you need to do is to make the main form appear on the taskbar as usual, but ensure that the other forms are not owned (in the Windows sense) by the main form. But they need to be owned to avoid having them in the taskbar. So you can make the hidden application window be the owner using the CreateParams method, like so:
procedure TOverlappedPopupForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := Application.Handle;
end;
Although you state otherwise in the comments, when I do this I find that the popup form is indeed hidden when I minimize the main form. And it is shown again when the main form is restored. Thus I think this does solve your problem completely.
I haven't got Delphi open now, but would setting
mainform.formstyle := fsStayOnTop
and show the child form with
childform.show;
work?
or else try using SetWindowPos() and setting the hWndInsertAfter property to something like HWND_TOPMOST on the main form
Well, my problem is as follows:
I have a Delphi 5 application that I'm essentially porting to Delphi 2010 (replacing old components with their latest versions, fixing the inevitable Ansi/Unicode string issues, etc.) and I've run into kind of a hitch.
Upon creation of one of our forms, an access violation happens. After looking it over, I've come to the conclusion that the reason for this is because one of the setters called in Create attempts to change a property of an object on the form that hasn't been created yet.
I've trimmed it down a little, but the code basically looks like this:
In form declaration:
property EnGrpSndOption:boolean read fEnGrpSndOption write SetGrpSndOption;
In form's Create:
EnGrpSndOption := false;
In Implementation:
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
btGrpSnd.Visible := Value;
end;
By tossing in a ShowMessage(BooltoStr(Assigned(btGrpSend), true)) right before btGrpSnd.Visible := Value, I confirmed that the problem is that btGrpSnd hasn't been created yet.
btGrpSend is an LMDButton, but I'm pretty sure that isn't quite relevant as it hasn't even been created yet.
While I realize I probably should only assign a value after confirming that the control is assigned, this would just result in the value set in create not being set to the actual control.
So what I want to do is find a way to make certain that all the controls on the form are created BEFORE my Create is run.
Any assistance in doing this, or information regarding how Delphi creates forms would be appreciated.
It worked back in Delphi 5, so I imagine the cause of this should be mentioned somewhere among the lists of changes between versions. Delphi 2010 is quite a bit newer than Delphi 5 after all.
Like Tobias mentioned (but advocates against) you can change the creation order (right at the form at change the creation order).
But you can also in the setter method check if the form is creating (csCreating in form.componentstate). And if it is you have to store that property value yourself, and handle it in AfterConstruction.
From your comment that you're getting an AV when placing it at design time, that means there's a problem with the control itself and it hasn't been properly ported forward. To reproduce it at runtime under controlled circumstances, you need to write a little program like this:
Make a new VCL app with a single form. Place a TButton on the form. On the button's OnClick, do something like this:
var
newButton: TLMDButton;
begin
newButton := TLMDButton.Create(self);
newButton.Parent := self;
//assign any other properties you'd like here
end;
Put a breakpoint on the constructor and trace into it until you can find what's causing the access violation.
EDIT: OK, from looking at the comments, I think we found your problem!
A form's subcontrols are initialized by reading the DFM file. When you changed your control to a TCustomForm, did you provide a new DFM to define it? If not, you need to override the form's constructor and create the controls and define their properties manually. There's no "magic" that will initialize it for you.
Your Create is always called first, before the ancestor constructor. That's just how constructors work. You should be able to call the inherited constructor before you do the rest of your initialization:
constructor MyForm.Create(Owner: TComponent);
begin
inherited;
EnGrpSndOption := False;
end;
However, there's a better way of indicating what it is you're trying to make happen. Your class is loading properties from a DFM resource. When it's finished, it will call a virtual method named Loaded. It's usually used to notify all the children that everything is ready, so if any of them hold references to other children on the form, they know it's safe to use those references at that point. You can override it in the form, too.
procedure MyForm.Loaded;
begin
inherited;
EnGrpSndOption := False;
end;
That generally shouldn't make much difference in your case, though. Loaded is called from the constructor right after the form finishes loading itself from the DFM resource. That resource tells the form all the controls it should create for itself. If your button isn't being created, then it's probably not listed correctly in the DFM. It's possible for controls to be listed in the DFM that don't have corresponding fields in the class. On the other hand, if there's a published field that doesn't have a corresponding entry in the DFM, the IDE should warn you about it and offer to remove the declaration each time you bring it up in the Form Designer. View your DFM as text and confirm that there's really an entry for a control named btGrpSnd.
Is this good enough to get you going:
if Assigned(btGrpSnd) and btGrpSnd.HandleAllocated then
btGrpSnd.Visible := ...
I see 2 possibilities: check if btGrpSnd is nil before assigning Value to the Visible property. If it's nil, you could either:
not set the property
create btGrpSnd
I would not mess around with the creation order. It's more complicated and may break with further changes.
from your comment: you can check wether you are in design or in runtime mode. Check wether your are in designtime before setting the visibility.
if not (csDesigning in Componentstate) then
begin
btGrpSnd:=Value;
end;
Answer to your comment:
go for this:
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
if btGrpSnd<>nil then btGrpSnd.Visible := Value;
end;
and one additional property setting btGrpSnd. If it is set to a value <> nil, set the visibility safed in fEnGrpSndOption as well.
If there's no need to set btGrpSnd outside Myform, create a init-procedure that creates everything. E.g.:
constructor Myform.Create(...)
begin
init;
end;
procedure init
begin
btGrpSnd:=TButton.Create;
...
end;
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
if btGrpSnd<>nil then init;
btGrpSnd.Visible := Value;
end;
This is still better then depending on some changed init-code-hack that may break in the future.