Delphi destroy dynamic control onDestroy of the parent form - delphi

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.

Related

Delphi TForm constructor

I have already found something on stackoverflow but it doesn't really solve my doubt. I know that the correct way to create an object is, after the creation, surround the code in a try-finally block. But what about:
procedure TForm3.FormCreate(Sender: TObject);
begin
a := TClassX.Create;
end;
And then call:
procedure TForm3.FormDestroy(Sender: TObject);
begin
a.Free;
end;
Where a: TClassX; is a public declaration inside TForm3 class. Should I create the constructor and the destructor for the form, or I can use the code above? Is it safe?
The try/finally is there, or at least something equivalent. It just exists outside your code, higher up the call stack. Something like:
Form1 := TForm1.Create(nil);
try
// do stuff
finally
Form1.Free;
end;
Your OnCreate and OnDestroy handlers are called from the constructor and destructor, respectively, and so are protected.
So long as everybody plays by the rules nothing leaks. And the rules here are that objects created in the constructor and to be destroyed in the destructor. Whoever actually creates the object is responsible to ensure that it is destroyed no matter what. But that's the task of the consumer of your class rather than you.
I had some issues with this events some time ago so I would not recommend to use OnCreate/OnDestroy events in your application. Here are just some cases I remember:
VCL by default handles exception from OnCreate/OnDestroy events. Actually if a:=TClassX.Create; will generate an exception, it will be shown by application exception handler but the form will be created successfully keeping "a" variable equals to nil. This may lead to Access Violations if you try to access this variable later.
Depending on OldCreateOrder these events may be called either from constructor/destructor or from AfterConstruction/BeforeDestruction methods. It may also lead to Access Violations if you occasionally change OldCreateOrder in form descendants
And even more, it looks strange for me when you are using events instead of virtual functions. In most cases events are used to delegate the functionality to another objects (for example you assign a form's method to a button's OnClick event).

Delphi 7 - When to use .create(Application) and when to use .create(nil)?

I've read a LOT of stuff lately regarding this, but never found a final answer.
So, for example if I write:
Form1 := TForm1.Create(Application);
the aplication should be responsible for freeing the form from memory right?
why then people usually do as follows:
Form1 := TForm1.Create(Application);
Form1.ShowModal;
Form1.Free;
??
Saw in some places that if you try to "free" an object that was already freed you would get an EAccessviolation msg, but as I tested it is not always true.
So PLEASE, How this actually works??
This EAccessviolation thing is driving me crazy, how can I understand this thing completely?? where do I find this precious information!??
The general rules are:
If you're going to free it yourself, use nil as the owner.
If you're not going to free it yourself, assign an owner that will take the responsibility to free it.
So, if your code is something like this:
Form1 := TForm1.Create(...)
Form1.ShowModal;
Form1.Free;
You should write it with nil as the owner, and protect it in a try..finally block:
procedure TForm1.Button1Click(Sender: TObject);
var
AForm: TForm2;
begin
AForm := TForm2.Create(nil);
try
AForm.ShowModal;
finally
AForm.Free; // You know when it will be free'd, so no owner needed
end;
end;
If on the other hand, you're going to leave it around for a while, assign an owner that can take care of freeing it later:
procedure TForm1.Button1Click(Sender: TObject);
var
AForm: TForm2;
begin
AForm := TForm2.Create(Application);
// Here you don't know when it will be free'd, so let the
// Application do so
AForm.Show;
end;
Neither of these techniques will cause an access violation if done the way I've demonstrated here. Note that in both cases, I did not use the IDE-generated Form2 variable, but used a local one instead to avoid confusion. Those IDE-generated variables are evil (except for the required Form1 or whatever you name it that represents the main form, which must be auto-created and owned by the Application). Other than the var for the main form, I always delete that auto-generated variable immediately, and never auto-create anything except possibly a datamodule (which can be autocreated before the main form without any problem, as a datamodule cannot be the main form).
The task of a component's Owner is to destroy all owned components when you the owner are being destroyed.
The Application object is destroyed upon termination and so if you are relying on it to destroy your form, that won't happen until termination.
The key point here is the assigning an owner controls both who destroys the owned component, but also when it is destroyed.
In your case you have a modal form that you want to have a short life. Always write them like this:
Form := TMyModalForm.Create(nil);
try
Form.ShowModal;
finally
Form.Free;
end;
There's no point giving them an owner since you explicitly destroy it. And make sure that Form is a local variable.
It won't hurt particularly if you did pass an owner. It would just be wasteful as the owner was notified of its responsibility, and then notified that it was no longer responsible.
But if you did this:
Form := TMyModalForm.Create(Self);
Form.ShowModal;
then each time you showed the modal form you'd leak a form that would not be destroyed until the owning form was destroyed. If you made Application the owned, the modal forms would be leaked until termination.
Ownership between forms is reasonable for, say, the main form and a modeless relative that lives as long as the main form. The modeless form can be owned by the main form and then destroyed automatically when the main form is destroyed.
But if the main form holds a reference to the modeless form then I'd probably just have it unowned and explicitly destroyed from the main form's destructor.
#dummzeuch makes the good point that if you set Position to poOwnerFormCenter, then the framework expects you to provide a form as the owner. In my view, this is a poor design which conflates lifetime management with visual layout. But that is the design, so you are compelled to go along with it. There is though nothing to stop you explicitly destroying an owned component. You can do this:
Form := TMyModalForm.Create(Self); // Self is another form
try
Form.Position := poOwnerFormCenter;
Form.ShowModal;
finally
Form.Free;
end;
When you destroy the form, its owner is notified, and the destroyed form is removed the owner's list of owned components.
The main form itself is interesting. It has to be owned by Application since the main form has to be created by calling Application.CreateForm. That's the only time you should call Application.CreateForm. And the main form is usually the only form you should have owned by Application. Especially if you adopt the policy of having other forms unowned, or owned by the forms that spawn them.
But if you let the main form be destroyed at termination, when Application is destroyed, then you can bee caught out. I've experienced intermittent runtime errors at termination when coded that way. My remedy is to explicitly destroy the main form as the final act of the main .dpr file's body. That is, destroy the main form after Application.Run returns.

Open and close vcl form

Right now i have 2 forms. On Form1 i open up Form2 like this:
procedure TForm1.Action1Execute(Sender: TObject);
var
Form2: TForm2;
begin
Form2 := TForm2.Create(Form2);
Form2.ShowModal;
Form2.Free;
end;
Now i want to close the Form2 with a button on it. So i tried
procedure TForm2.cancelBtnClick(Sender: TObject);
begin
Form2.Close;
end;`
But that only gives me Access violation error when i click that button. What i am doing wrong?
The normal way to do this is to do
procedure TForm1.Action1Execute(Sender: TObject);
begin
with TForm2.Create(nil) do
try
ShowModal;
finally
Free;
end;
end;
and, if TForm2 contains a OK button, this should have the ModalResult property set to mrOK at design-time. Use the object inspector to set this. You probably also want to set Default to True. Now you can 'click' the OK button by pressing the Enter key on your keyboard!
In addition, if there is a Cancel button in the dialog, this should have ModalResult set to mrCancel and Cancel set to True. Now you can 'click' the Cancel button by pressing the Escape key on your keyboard!
A button with a ModalResult value will automatically close a modal dialog box.
Since the form is showing modally the correct solution is to set ModalResult := mrCancel in your button click handler. The shortcut is to set the ModalResult property of the button to mrCancel and then you don't even need the event handler.
Note that your form is being created incorrectly. You are passing the unassigned variable Form2 as the Owner parameter to the constructor. I expect this is the cause of the access violation.
You should pass another form, Application or nil, for example. In fact in this case you may as well pass nil so that the code should read:
Form2 := TForm2.Create(nil);
try
Form2.ShowModal;
finally
Form2.Free;
end;
If you pass an owner then the form will be destroyed when the owner is destroyed. Since you are destroying it yourself, you don't need to pass an owner.
That said, it is sometimes useful to set the owner, for example if you are using one of the Position property values that sets the form's position based on the owner's position. If so then I recommend passing Self in this instance which is a TForm1 object reference.
The other two answers discuss other ways to solve your problem.
I am going to point out the cause of the problem.
You have two variables named Form2. One contains the form and the other is uninitialized most likely Nil. The cause of the A/V is because your accessing the Nil variable.
When working with in a class you should avoid using the variable name of the class instead you can reference it's members directly such as calling Close; without referencing the variable. To keep things clear you can also prefix it with self. so replace form2.close; with self.close;
In your second code, you're already in form 2, so you would only need to do
procedure TForm2.cancelBtnClick(Sender: TObject);
begin
Close;
end;
If in your first code you created a variable you didnt use the generic Form2 variable made as a public variable in the form, its not then allocated to the form you called. So it will give errors. If at the top of the unit, you removed the "var Form2: TForm2;" line, you'd see it complain about the lack of variable for that line, change it to close, it will go away and so will your error.
I don't know if you already resolved that question or created a new code to avoid the problem, but what I think is happening (and you didn't showed all your code to confirm) is that you created bad code on the OnActivate main form event.
When you exit the second form, you return to your mainform and it "actvates" again.
Probably you manipulated an object that do not exists anymore.

Delphi - Creating controls before form Create is run?

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.

Problem setting parented to new form in DLL

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.

Resources