Two days ago I gave an accepted answer for this question (that is what bothering me the most):
newtabsheet:=ttabsheet.Create(PageControl1);
NewTabSheet.PageControl := PageControl1;
newtabsheet.Caption:='tab1';
i1:=tabs.Count;
tabs.Add(newtabsheet);
I have analysed this code the best I can, and these are my results.
Line 1: I create a ttabsheet with pagecontrol1 as the parent/owner (based on the constructor below).
constructor TTabSheet.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Align := alClient;
ControlStyle := ControlStyle + [csAcceptsControls, csNoDesignVisible,
csParentBackground, csPannable];
Visible := False;
FTabVisible := True;
FHighlighted := False;
end;
Then I stored a reference to it in the variable Newtabsheet (this remarque is based on an answer #David Heffernan gave to one of my questions).
Line 2: I used the reference to assign pagecontrol1 to the property .pagecontrol.
And this is the code of the setting method:
procedure TTabSheet.SetPageControl(APageControl: TPageControl);
begin
if FPageControl <> APageControl then
begin
if FPageControl <> nil then
FPageControl.RemovePage(Self);
Parent := APageControl;
if APageControl <> nil then
APageControl.InsertPage(Self);
end;
end;
Based on this I think (maybe wrong) it is comparing the existing parent to the new passed parameter, if there is a different then if the parent<>nil removes this newtabsheet from the previous pagecontrol (visual affects) and assign it to the new parent/owner, then if the new parent<>nil insert it in the new pagecontrol(visual affects).
So this line is unnecessary (maybe wrong), because I already did that in the first line.
Line 3: I use the reference to assign the caption of the tabsheet with 'tab1'.
Line 5: I use the add method to store a reference to the tabsheet in the tabs list tabs:TList<ttabsheet>; a generic.
Here is where things start to fly over my head.
I think that the parent/owner is tabs, but the pagecontrol1 is still showing the tab (based on the accepted answer to this question changing the parent visually removes the tabsheet from pagecontrol1), but it does not.
Now this could be wrong, but again if it is just a reference then why when I delete the tabsheet from pagecontrol1 by doing PageControl1.ActivePage.free the tabs.count remains constant.
And if I delete the tabsheet from tabs then the tabsheet on the pagecontrol1 is not deleted (removed visually).
From this question I understood that generics becomes the parent/owner and that you do not need to worry about freeing tabsheet from pagecontrol1, because tabs is the parent and you only need to free it from tabs.
My question: What is happening in this code and why I'm facing this behavior?
Clarification
What is driving the question is when I delete the ttabsheet in the pagecontrol why it is not raising an error when I try to use the reference to it in the list are they two different objects.
The Owner is responsible for memory management. Assigning the TPageControl as the Owner of the TTabSheet means the TPageControl will destroy the TTabSheet when the TPageControl is destroyed. The Owner and ownee contain pointers to each other so they can notify each other of important events related to memory management.
The Parent is responsible for window management and visual presentation. Assigning the TPageControl as the Parent of the TTabSheet means the TTabSheet's window is a child of the TPageControl's window, and will be displayed inside the client area of the TPageControl's window. The Parent and child contain pointers to each other so they can notify each other of important events related to window management.
The Owner and Parent are two different things. They can be the same object, but they don't have to be (case in point - components created at design-time are always owned by the TForm, TFrame, or TDataModule that is being designed, but can be children of container components, like TPanel, etc).
The TList is responsible for nothing. It is just a dynamic array of arbitrary values, which in this case happen to be TTabSheet pointers. Nothing more. There is no Owner/Parent relationship between the TList and TTabSheet at all.
When the TTabSheet is destroyed, there are relational links between itself and its TPageControl. The TTabSheet removes itself from its TPageControl's management. It is no longer owned by the TPageControl, and is no longer a child of the TPageControl. The two objects clear their pointers to each other.
There is no relational link between the TTabSheet and the TList at all. The TTabSheet has no concept that the TList even exists. When the TTabSheet is added to and removed from the TList, the TList is simply adding/removing a pointer to the TTabSheet object. The TList does not notify the TTabSheet about the insertion/removal. And there is no pointer from the TTabSheet to the TList, so when the TTabSheet is destroyed, there is no mechanism in place that allows the pointer to that TTabSheet to be removed from the TList. The pointer remains in the list, and you can continue using the pointer (for comparisons, etc) as long as you do not dereference it to access the destroyed TTabSheet.
If you want to remove the TTabSheet pointer from the TList automatically when the TTabSheet is destroyed, you need to call the TTabSheet's FreeNotification() method. Your Notification() callback can then remove the TTabSheet pointer from the TList in response to opRemove notifications, eg:
TMyForm = class(TForm)
...
private
tabs: TList<TTabSheet>;
...
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override; // <-- ADD THIS
...
end;
...
procedure TMyForm.DoSomething;
var
NewTabSheet: TTabSheet;
...
begin
...
NewTabSheet := TTabSheet.Create(PageControl1);
NewTabSheet.PageControl := PageControl1;
NewTabSheet.Caption := 'tab1';
tabs.Add(NewTabSheet);
NewTabSheet.FreeNotification(Self); // <-- ADD THIS
...
end;
procedure TMyForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent is TTabSheet) then
tabs.Remove(TTabSheet(AComponent)); // <-- HERE
end;
If you want the TTabSheet to be removed from its TPageControl automatically when its pointer is removed from the TList, switch to TObjectList instead. Its OwnsObjects property is true by default, so it will destroy the TTabSheet when the pointer is removed from the list using the Remove() or Delete() method. The Extract() method will not destroy the TTabSheet, so use that instead of Remove() in your Notification() callback, eg:
TMyForm = class(TForm)
...
private
tabs: TObjectList<TTabSheet>;
...
end;
...
procedure TMyForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent is TTabSheet) then
tabs.Extract(TTabSheet(AComponent)); // <-- HERE
end;
Related
I have sample code below
can someone explain why delphi compiler not release all memory allocation
for object so nil checking is always true. im using release method for destroying TLoginForm im not using free method because that gave me exception when some interfaced event called before free or after it.
type
TMainForm = class(TForm)
{some stuf}
public
procedure Create(AOwner: TComponent); override;
procedure doLogout();
end;
TChildBase = class(TForm)
{some stuf}
public
procedure Create(AOwner: TComponent); override;
end;
TLoginForm = class(TChildBase)
public
procedure doLogin();
end;
var
MainForm: TMainForm; {<== created automaticaly at runtime}
LoginForm: TLoginForm; {<== created at create event in TMainForm}
implementation
{TLoginForm}
procedure TLoginForm.doLogin;
begin
if true then
begin
{ Do Interfaced Event to main form }
Release;
end;
end;
procedure TMainForm.Create(AOwner: TComponent);
begin
inherited;
FormStyle := fsMDIForm;
LoginForm := TLoginForm.Create(Application); {Create login form}
end;
procedure TMainForm.doLogout;
begin
{ Do Interfaced Event to Child Form except TLoginForm to close}
if LoginForm <> nil then {<== this still alocated at memory}
LoginForm := TLoginForm.Create(Application)
else
LoginForm.Show; {<== Error raised here.}
end;
Release() is just a delayed destruction. It posts a CM_RELEASE message to the Form, and then the Form calls Free on itself when it processes that message.
Freeing a Form object (or any other object, for that matter) does not automatically set any pointers to that object to nil. You have to do that manually in your own code. In this case, you can set the global LoginForm pointer to nil in TLoginForm's destructor or OnDestroy event.
You might also consider using the TLoginForm.OnClose event instead of calling Release() directly. Have doLogin() call Close(), and when the Form is actually closed, set the event's Action parameter to caFree so the Form frees itself (via Release()). You still have to set the global LoginForm pointer to nil manually, though.
Nowhere in your code do you set the variable LoginForm to nil. So it retains its value even after the object it refers to has been destroyed. If you wish this variable to be set to nil you must explicitly do that.
You should find this answer to a related question helpful: https://stackoverflow.com/a/8550628/
In essence, the issue behind your question can perhaps better be understood with this simple example:
obj := TObject.Create;
obj.Free;
Assert(Assigned(obj));
Destroying an object does not clear the variables that refer to it.
Yes, in your code you use Release rather than Free but the fundamental concept is exactly the same.
Is there any way to control the order of which a Delphi form is destroying its components?
I got AVs when the form is destroying because it is destroying a component before the other component which accesses the first component.
Currently, I have no way to avoid AVs except free the first component in finalization section
You will have to call FreeNotification() on the control being referenced and override the protected Notfication() method of your control that is referencing it.
Say you have a property that links to another component (say, a TEdit):
property EditControl: TEdit read FEdit write SetEdit;
Then, if your property is set to link to such a component, you tell it that you want to be notified when it is freed, by calling its FreeNotification() method:
procedure TMyControl.SetEdit(Value: TEdit);
begin
if FEdit <> Value then
begin
if FEdit <> nil then
FEdit.RemoveFreeNotification(Self);
FEdit := Value;
if FEdit <> nil then
FEdit.FreeNotification(Self);
end;
end;
This means that your Notification() procedure will be called when the TEdit is destroyed. You will have to override it:
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
...
procedure TMyControl.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FEdit) then
FEdit := nil;
end;
That way, you will know when you can access the TEdit component, and when not to anymore. If FEdit is nil, you should not access it.
Examples taken from:
http://mc-computing.com/Languages/Delphi/ComponentNotification.html
Documentation:
http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TComponent.FreeNotification
I did some search and some tests and found how to control the Delphi form components destroy order.
There are two ways, at design time and at runtime:
At design time, Edit the form's DFM, move the first component's object block as first block before any other object blocks of any other components that must be freed after the first one.
At runtime, in form OnCreate, Change ComponentIndex property of first component to any value lower than the other components which depend on it, Say 0, this will make it last one to be freed.
Example: FirstComp.ComponentIndex := 0;
That's it!
As you can see, Delphi frees components in the opposite order of which they were added to the form (LIFO), But we can alter this order as I explained.
Thanks everyone for your valuable help. Appreciate it.
I create many forms on runtime using
Application.CreateForm(TForm2, Form2);
Form2.Show;
Now I need to change the Memo1.Font of them at once.
Form2.Memo1.Font:=newfont;
But only the latest created form's Memo1.Font changes. How can I change all?
I guess you're calling Application.CreateForm(TForm2, Form2); multiple times which reassigns the newly created form to your global Form2 variable, so later when you refer to Form2 you're referring to the last-created instance.
To access all instances of TForm2 in your application, you can use Screen.Forms property:
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TForm2 then
TForm2(Screen.Forms[I]).Memo1.Font := ...
The reason for this behaviour is, that component names should be unique,
You can not address multiple components by just one name!
In this case You will have to iterate through all components to find all TMemos.
This could look something like
var i,j: integer;
begin
// first find all Forms in Application
for i:=0 to Application.ComponentCount - 1 do
begin
if Application.Components[i] is TForm then
begin
with (Application.Components[i] as TForm) do
begin
// now find all TMemos and change the font
for j:=0 to ComponentCount-1 do
begin
if (Components[j] is TMemo) and (Components[j].Name = 'Memo1') then (Components[j] as TMemo).Font.Name := 'Arial';
end;
end;
end;
end;
This is a very general approach and You can easily adapt it to other components within Your application.
Another approach would be to memorize all created TMemos in an Objectlist when they are created the first time (then You could easily iterate the object list and change TMemos properties) but without knowing more about Your implementation it is hard to give a good advice.
I have a form containing a TFrame. The TFrame contains a ComboBox that is dynamically populated. Each ComboBox entry has an associated object. By the time the overridden destructor for the TFrame is called, the Items in the ComboBox have already been cleared without freeing their associated objects. This happens whether I drop the ComboBox on the form in designer view, or dynamically create it in code with either nil or the TFrame as its owner. I currently use the OnDestroy event of the containing TForm to call a clean-up procedure of the contained TFrame.
Is there a better way that would not need an explicit procedure call by the TFrame's container? Where ideally should the objects added dynamically to the ComboBox be freed?
You say that when the destructor for the TFrame is called, the Items of the ComboBox have already been cleared. That's not the case, ComboBox items are never cleared. When Items is destroyed by the ComboBox, they've got a count of only 0.
When you exit your application and the VCL destroys the form containing the frame and the ComboBox, the native ComboBox control is also destroyed by the OS since it is placed in a window being destroyed. When you later access the items to be able to free your objects in the frame destructor, the VCL have to recreate a native ComboBox control, having an item count of 0.
The solution I'd propose is easy. Don't leave freeing your frame to the framework, instead, destroy your frame in the OnDestroy event of your form. That would be before the underlying window of the form is destroyed, hence you'll be able to access your objects.
form unit
procedure TMyForm.FormDestroy(Sender: TObject);
begin
MyFrame.Free;
end;
frame unit
destructor TMyFrame.Destroy;
var
i: Integer;
begin
for i := 0 to ComboBox1.Items.Count - 1 do
ComboBox1.Items.Objects[i].Free;
inherited;
end;
You could utilize the TFrame's WM_DESTROY handler like this:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
TFrame1 = class(TFrame)
ComboBox1: TComboBox;
private
procedure WMDestroy(var Msg: TWMDestroy); message WM_DESTROY;
procedure FreeComboBoxItems;
public
constructor Create(AOwner: TComponent); override;
end;
implementation
{$R *.dfm}
constructor TFrame1.Create(AOwner: TComponent);
begin
inherited;
// Add some object items to the ComboBox
ComboBox1.AddItem('a', TButton.Create(nil));
ComboBox1.AddItem('b', TMemoryStream.Create);
ComboBox1.AddItem('c', TList.Create);
end;
procedure TFrame1.WMDestroy(var Msg: TWMDestroy);
begin
// Make sure the TFrame is actually destroying - not recreated
if (csDestroying in ComponentState) then
FreeComboBoxItems;
inherited;
end;
procedure TFrame1.FreeComboBoxItems;
var
I: Integer;
begin
OutputDebugString('TFrame1.FreeComboBoxItems');
with Self.ComboBox1 do
for I := 0 to Items.Count - 1 do
begin
OutputDebugString(PChar(Items.Objects[I].ClassName + '.Free'));
Items.Objects[I].Free;
end;
end;
end.
Another option is to create a Base ancestor TAppBaseForm class and a TAppBaseFrame for the entire application, and derive all your Forms as TAppBaseForm and all Frames as TAppBaseFrame.
This way the TAppBaseForm could notify all it's child TAppBaseFrame that the owner Form is destroyed on TAppBaseForm.FormDestroy event handler. At that point the ComboBox items are still valid (as described by Sertac Akyuz's answer).
Your question isn't really usefull, because - generally speaking - it is discouraged to store data (or objects in your case) in a GUI control. See also David's comment on how to change your design.
What makes the question kind of interresting to answer though is the difference between the combo box being a child of a form directly and being a child of another child of the form (your frame in this case). Apparently, the combo box items are destroyed before the destructor of that frame is called. Obvious alternatives to explore are then: overriding Frame.BeforeDestruction, overriding Frame.DestroyWindowHandle, overriding Frame.DestroyWnd, or catching WM_DESTROY in an overridden Frame.WndProc, but none of them is called before the items are already gone.
The next thing to try is to repeat this for the combo box. It turns out that when WM_DESTROY arrives at the combo box that the items are still there. However, beware of catching that message ónly when the control really is being destroyed, because the VCL might recreate a combo box frequently. Implement it using an interposing class for TComboBox, as follows:
unit Unit2;
interface
uses
Windows, Messages, Classes, Controls, Forms, StdCtrls;
type
TComboBox = class(StdCtrls.TComboBox)
protected
procedure WndProc(var Message: TMessage); override;
end;
TFrame1 = class(TFrame)
ComboBox1: TComboBox;
end;
implementation
{$R *.dfm}
{ TComboBox }
procedure TComboBox.WndProc(var Message: TMessage);
var
I: Integer;
begin
if (Message.Msg = WM_DESTROY) and (csDestroying in ComponentState) then
for I := 0 to Items.Count - 1 do
Items.Objects[I].Free;
inherited WndProc(Message);
end;
end.
Now, to answer your question: "Is this a better way?"
Yes it is, because it offers assurance of the object's destruction at the frame's level. In other words: you don't have to remember to deal with this for every instance seperately.
And no it is not, because this solution requires that the objects in the combo box are allowed to be freed in whatever circumstance which restricts usage to an unnecessary extra boundary.
So, is this answer usefull? Well, if it prevents you from using your current approach, then it is.
Besides, I also found another alternative by setting the frame's Parent property to nil in the containing form OnDestroy handler:
procedure TForm2.FormDestroy(Sender: TObject);
begin
Frame1.Parent := nil;
end;
In this case, you can safely destroy the objects stored in the combo box within the frame's destructor. But this solution is even worse than your current one, because it is not descriptive. Then Frame1.FreeComboObjects is much better.
freeing Combobox.Items.Objects in destructor is too late.
so, according to previous answers it is better and safe to do that this way:
procedure TMyFrame.ClearCBObjects;
var
i: Integer;
begin
for i := 0 to ComboBox1.Items.Count - 1 do
ComboBox1.Items.Objects[i].Free;
end;
destructor TMyFrame.Destroy;
begin
//Free none Component objects
inherited;
end;
destructor TMyForm.Destroy;
begin
MyFrame.ClearCBObjects;
inherited;
end;
This will sound against the nature of MDI.. I need to show a MDI form (FormStyle=fsMdiChild) as modal sometimes. And I also need to access the part between Application.CreateForm and OnShow event of another MDI form, i.e.
Application.CreateForm(Form2,TForm2); // but don't set form2's visible property true.
Form2.caption:='not working example';
Form2.SomeMagicToSetVisibleTrue;
Any ideas?
For your first problem: Add another constructor, for example CreateAsMDI, like this:
constructor TModalAndMDIForm.CreateAsMDI(AOwner: TComponent);
begin
f_blChild := true;
GlobalNameSpace.BeginWrite;
try
inherited CreateNew(AOwner);
if(not(csDesigning in ComponentState)) then begin
Include(FFormState, fsCreating);
try
FormStyle := fsMDIChild;
if(not(InitInheritedComponent(self, TForm))) then
raise Exception.CreateFmt('Can't create %s as MDI child', [ClassName]);
finally
Exclude(FFormState, fsCreating);
end;
end;
finally
GlobalNameSpace.EndWrite;
end;
end;
In the normal constructor just set the variable f_blChild to false and call the inherited create.
You need two more things, rather self explaining:
procedure TModalAndMDIForm.Loaded;
begin
inherited;
if(f_blChild) then
Position := poDefault
else begin
Position := poOwnerFormCenter;
BorderStyle := bsDialog;
end;
end;
//-----------------------------------------------------------------------------
procedure TModalAndMDIForm.DoClose(var Action: TCloseAction);
begin
if(f_blChild) then
Action := caFree;
inherited DoClose(Action);
end;
Now you can call the form modal, if created with the standard constructor, and as MDI child, if created with CreateAsMDI.
If you include this in your form's declaration
property IsChild: boolean read f_blChild;
you can even do things depending on whether the form is an MDI child or not, just interrogating the isChild property.
As for your second problem: do not use Application.CreateForm, but create your form yourself:
Here the two creations for modal and MDI:
//Modal
frmDialog := TMyForm.Create(self);
// Your Code
frmDialog.ShowModal;
frmDialog.Release;
//MDI-Child
frmDialog := TMyForm.CreateChild(self);
// Your code
frmDialog.Show;
I have translated this answer form an article on the site DelphiPraxis.
The simplest method is to create a trivial subclass of the form, and set
FormStyle = fsMDIChild
AND
Form.Visible = False
in the property inspector. This is tried and tested!
At least for Delphi 2007 and 2009 creating the MDI child form invisible is easy. For the early Delphi versions (where it was impossible to set Visible to False in the property inspector) you just have to provide a handler for the OnCreate event and access a protected field of the class:
procedure TMDIChild.FormCreate(Sender: TObject);
begin
FFormState := FFormState - [fsVisible];
end;
This will disable the automatic showing of the MDI child. After you are done with your other initialisations you can simply Show it or set Visible to True.
I will not try to answer your question about modal MDI child forms, as this violates the conventions of the Windows platform.
No answers above actually does the job required. The best answer is wrong as well, because of following:
Opening the first form;
Maximize it;
Now say this form calls another form (mdi);
When it is constructed the same way you will get buggy layout... (it will mdi child, but not maximized, however the first will be still maximized). So a buggy state.
If you really need to decide in run-time whether it's a fsMDIChild or fsNormal you need to apply following approach.
You have form which is stored as fsNormal in design (or vice-versa);
Override the InitializeNewForm method;
Set the value of FFormStyle to fsMDIChild (as shown below).
...
TYourForm = class(TForm)
...
protected
procedure InitializeNewForm; override;
...
procedure TYourForm.InitializeNewForm;
var
FS: ^TFormStyle;
begin
inherited;
if DoYourCheckForMDI then
begin
FS := #FormStyle;
FS^ := fsMDIChild;
end;
end;