i'm writing a delphi 2009 app that uses a TTreeView on a docking panel.
i saw i could make big simplifications in my app if i subclassed the TTreeNode. the tree view it's on is placed on a docking panel.
TInfoTreeNode=class(TTreeNode)
private
// remember some stuff
public
end;
procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
NodeClass:=TInfoTreeNode;
end;
i think i've hit a wall though...each "TInfoTreeNode" instance needs to remember things about itself. since the handles are freed when the panel containing the TTreeView auto-hides, the classes are destroyed.
that's a problem because then everything the classes knew is then forgotten.
is there a way around this (other than reloading every TInfoTreeNode from the database again)?
thank you!
IIRC, the Tag Data property on each TTreeNode instance is preserved through the handle rebuild.
You could either use this as an index into a List containing objects with additional information, or use type-casting to store an object reference and access the objects directly.
the problem is caused by the wrong implementation of your custom TreeNode - it doesn't preserve its information when the TreeView's parent window gets recreated after it has been hodden. As a solution, create a TTreeView descendant and override its DestroyWnd method, to preserve your custom values. For example, take a look at how the TCustomTreeView.DestroyWnd method is implemented.
Having looked at TCustomTreeView.DestroyWnd like Joe Meyer proposes, I would suggest you revert to using the TreeNode.Data property, and store a reference to objects of a new class inheriting from TObject directly. The OnDeletion event of the TreeView offers a good spot to put the destruction code: "TMyObject(Node.Data).Free;"
Usage is pretty similar except you'll need to use "TMyObject(Node.Data)" instead of "TMyNode(Node)". A warning though: experience has taught me to pay close attention not to forget the ".Data" part, since "TMyObject(Node)" will not throw a compile error and raise access violations at run-time.
thank you all for your replies!
i have for 10 years been using the tree view using TTreeNode's data property. i wanted to be free of:
setting the Data property
creating/destroying the "data" object in a manner so there are no memory leaks
i have used the Data property for an ID number in the past as well.
today, my nodes have GUIDs to find their data in the database so they don't "fit" into the Data property anymore.
using a descendant of TTreeNode seems to have addressed my wishes nicely but in order to make that work nicely i had to do a few things:
handle TTreeView.OnCreateNodeClass event
handle TTreeView.OnDeletion event to retrieve latest data from the nodes before they are destroyed
handle TTreeView.OnAddition event to: 1) maintain a simple list of the nodes 2) set the node's Data property so we can use it to find the place in the list allocated for storing it's data.
here's the code:
TInfoTreeNodeMemory=record
...
end;
TInfoTreeNode=class(TTreeNode)
private
m_rInfoTreeNodeMemory:TInfoTreeNodeMemory;
public
property InfoTreeNodeMemory:TInfoTreeNodeMemory read m_rInfoTreeNodeMemory write m_rInfoTreeNodeMemory;
end;
TInfoTreeNodeMemoryItemList=class
private
m_List:TList<TInfoTreeNodeMemory>;
public
constructor Create;
destructor Destroy; override;
procedure HandleOnDeletion(Node: TInfoTreeNode);
procedure HandleOnAddition(Node: TInfoTreeNode);
end;
TfraInfoTree = class(TFrame)
tvInfo: TTreeView;
procedure tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
procedure tvInfoDeletion(Sender: TObject; Node: TTreeNode);
procedure tvInfoAddition(Sender: TObject; Node: TTreeNode);
private
m_NodeMemory:TInfoTreeNodeMemoryItemList;
...
procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
// THIS IS VITAL!
NodeClass:=TInfoTreeNode;
end;
procedure TfraInfoTree.tvInfoDeletion(Sender: TObject; Node: TTreeNode);
begin
m_NodeMemory.HandleOnDeletion(TInfoTreeNode(Node));
end;
procedure TfraInfoTree.tvInfoAddition(Sender: TObject; Node: TTreeNode);
begin
m_NodeMemory.HandleOnAddition(TInfoTreeNode(Node));
end;
g_icTreeNodeNotInList=MAXINT;
procedure TInfoTreeNodeMemoryItemList.HandleOnDeletion(Node: TInfoTreeNode);
var
iPosition:integer;
begin
iPosition:=integer(Node.Data);
if iPosition=g_icTreeNodeNotInList then
raise Exception.Create('Node memory not found!')
else
// we recognize this node; store his data so we can give it back to him later
m_List[iPosition]:=Node.InfoTreeNodeMemory;
end;
procedure TInfoTreeNodeMemoryItemList.HandleOnAddition(Node: TInfoTreeNode);
var
iPosition:integer;
begin
// "coat check" for getting back node data later
iPosition:=integer(Node.Data);
if iPosition=g_icTreeNodeNotInList then
begin
// Node.Data = index of it's data
// can't set Node.Data in OnDeletion so we must assign it in OnAddition instead
Node.Data:=pointer(m_List.Count);
// this data may very well be blank; it mostly occupies space; we harvest the real data in OnDeletion
m_List.Add(Node.InfoTreeNodeMemory);
end
else
// we recognize this node; give him his data back
Node.InfoTreeNodeMemory:=m_List[iPosition];
end;
very cool...it meets all my objectives!
to add a node to the tree, all i need to do is:
// g_icTreeNodeNotInList important so the "coat check" (TInfoTreeNodeMemoryItemList)
// can recognize this as something that's not in it's list yet.
MyInfoTreeNode:=TInfoTreeNode(tvInfo.Items.AddChildObject(nParent, sText, pointer(g_icTreeNodeNotInList))));
Related
I have two separate requirements to detect if an object is being created from a stream while in the Create / AfterConstruction code.
In the first case I have an existing object which is being refeactored so it is implemented as a component to allow consuming users to drop the component on a form or data module. One of the properties of this component is a Uuid which needs to be assigned uniquely to each object instance, and needs to remain unique for that object instance across different runs of the program. Internally the Uuid is held in our own class but we present a UuidString property to the user in the IDE. I need to know whether to allocate a Uuid and register the component on first creation, or wait until the Loaded routine (which is never called if it's not read from a stream).
In the second case I have a set of components that provide an 'OnReady' event to the application. Once the object is completely initialised (which could be asynchronous) the event is called. If the object is being streamed then I can override Loaded method to undertake additional configuiration, but if it's not being streamed Loaded will never be called and I should start the additional work in AfterConstruction.
Looking at the documentation I though I could use:
if( not (csLoading in Self.ComponentState) ) then
...
Or, to catch newly created objects in the designer specifically:
if( (csDesigning in Self.ComponentState) And
not (csLoading in Self.ComponentState) ) then
...
However having looked into the Code (I'm not really a Delphi programmer by background) I see that csLoading is only set after the Create / AfterConstruction has executed.
During Create / AfterConstruction execution is there anyway I can tell if Loaded is going to be called?
I have realised that all components created by streaming will have Owner<>nil but it's expected that components created at runtime would normally have Owner<>nil as well.
My only thought at the moment is to see if the owner is loading with something like:
if( (Self.Owner<>nil) And (not (csLoading in Self.Owner.ComponentState)) ) then
...
Is this the correct approach? Or is there a better 'Delphi Way' to do it?
Assuming your property is named UUIDString backed by a field FUUIDString. Then this approach should work:
type
TMyComponent = class(TComponent)
private
FUUIDString: string;
function GetUUIDString: string;
protected
procedure Loaded; override;
public
property UUIDString: string read GetUUIDString write FUUIDString;
end;
function TMyComponent.GetUUIDString: string;
begin
if FUUIDString = '' then
FUUIDString := CreateNewUUIDString;
Result := FUUIDString;
end;
procedure TMyComponent.Loaded;
begin
inherited;
RegisterUUIDString(UUIDString);
end;
If UUIDString is loaded during the stream reading it will contain the stored value. Otherwise the register call inside Loaded will generate a new one.
You were pretty close to a solution. You just needed to override the correct procedure.
I’m using "CreateWnd" for the following reasons:
I need to initialise images that require the parent (Panel) to have a Handle ready, so this procedure is the right place to do that.
It is called when ComponentState is established (with csDesigning and/or csLoading).
Any Published Property is available (has been streamed in from the DFM) at this point.
It is only called once.
The component I’m working on is a “Picture- Button”, made form a TCustomPanel with a TSpeedButton on it and up to 4 images also on the panel.
So I wanted to put initial (default) images on the panel, which requires determining when the component is first created (dropped on the form).
See some of the code below >>
………
protected
{ Protected declarations }
procedure CreateWnd; override;
procedure DoInitialConfig;
………
procedure TJEPicButton.CreateWnd;
begin
inherited;
if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
begin // Initial State Only
DoInitialConfig;
end;
// More code here....
end;
procedure TJEPicButton.DoInitialConfig;
begin
// Load initial (default) image(s) on panel
// The user can (using Object Inspector) replace this image(s) with his own…
end;
I'm trying to find a universal** solution to extend the built-in Treeview/TreeNode by some features such as ToolTips per Node. So first I derived a TExtendedTreeNode = class(TTreeNode) and added a corresponding property which seems to work fine - I can add TExtendedTreeNodes with different ToolTips for each node.
For the next step, I want to use the TTreeView.OnMouseMove event to show the corresponding ToolTip, but what is the best solution to extend this functionality in a universal** way?
My idea was to use a class helper for TTreeView:
type
TTreeViewExtension = class helper for TTreeView
private
procedure ShowNodeToolTips(Sender: TObject; Shift: TShiftState; X, Y: Integer);
public
constructor Create(AnOwner: TComponent);
end;
...
constructor TTreeViewExtension.Create(AnOwner: TComponent);
begin
inherited Create(AnOwner);
ShowMessage('TTreeViewExtension.Create');
self.OnMouseMove := #self.ShowNodeToolTips;
end;
The code is compiled without warnings or errors, but this constructor is NOT executed on creation of a treeview in my form.
And yes, I'm using advancedrecords in objfpc mode in both, my form unit and my extension unit - in order to use the class helper:
{$mode objfpc}{$H+}
{$modeswitch advancedrecords+}
** "universal" means, I want to use the integrated controls from my Lazarus IDE at least for the TreeView control, but use the extended functionality without writing code twice.
Why don't you use the already available OnHint event to show these tooltips. The TTreeView.OnHint event already returns you reference to the tree node that is beneath the mouse cursor so you should not have any problem reading your custom hints (tooltips) from the node.
If the tips can be shown in a single line of text you can simply change the value of Hint variable that is exposed in this event method.
You can easily read such value from your Extended TreeNode by typecasting the Node constant returned by the event method to your TExtendedTreeNode class.
Don't forget to check if the node in question is indeed of the right class.
procedure TForm1.TreeView1Hint(Sender: TObject; const Node: TTreeNode;
var Hint: string);
begin
//Check to see if the node beneath the cursor is the extended node
if Node is TExtendedTreeNode then
//if it is change the hint text to the custom hint stored in the
//node itself
Hint := TExtendedTreeNode(Node).CustomHint
//Else change the hint to empty string so no hintbox will be shown
else Hint := '';
end;
And if you don't want any hint text to be shown and show your information in a different way you simply set the Hint value to an empty string.
procedure TForm1.TreeView1Hint(Sender: TObject; const Node: TTreeNode;
var Hint: string);
begin
//Set Hint to empty string in order to not show any hint box
Hint := '';
//Do some other code instead if you like
MessageBeep(0);
end;
Overview
This question is a second attempt based on this one I recently asked: How can I make a TList property from my custom control streamable?
Although I accepted the answer in that question and it worked, I soon realized that TCollection is not the solution or requirement I was looking for.
Requirements
To keep my requirements as simple and clear to understand as possible, this is what I am attempting to:
Derive a new custom control based on TCustomListBox
Replace the Items property with my own Items type, eg a TList.
The TList (Items property) will hold objects, each containing a caption and a image index property etc.
Ownerdraw my listbox and draw its icons and text etc.
Create a property editor to edit the Items at design-time.
With that in mind, I know how to create the custom control, I know how to work with TList or even TObjectList for example, I know how to ownerdraw the control and I also know how to create the property editor.
Problem
What I don't know is how to replace the standard listbox Items type with my own? well I kind of do (publishing my own property that shares the same name), only I need to make sure it is fully streamable with the dfm.
I have searched extensively on this subject and have tried studying code where TListView and TTreeView etc publishes its Items type but I have found myself more confused than ever.
In fact I came across this very old question asked by someone else on a different website which asks very much what I want to do: Streaming a TList property of a component to a dfm. I have quoted it below in the event the link is lost:
I recently wrote a component that publishes a TList property. I then created a property editor for the TList to enable design-time editing. The problem is that the TList doesn't stream to the dfm file, so all changes are lost when the project is closed. I assume this is because TList inherits from TObject and not from TPersistant. I was hoping there was an easy work around for this situation (or that I have misunderstood the problem to begin with). Right now all I can come up with is to switch to a TCollection or override the DefineProperties method. Is there any other way to get the information in the TList streamed to and from the dfm?
I came across that whilst searching keywords such as DefineProperties() given that this was an alternative option Remy Lebeau briefly touched upon in the previous question linked at the top, it also seemed to be the answer to that question.
Question
I need to know how to replace the Items (TStrings) property of a TCustomListBox derived control with my own Items (TList) or Items (TObjectList) etc type but make it fully streamable with the dfm. I know from previous comments TList is not streamable but I cannot use TStrings like the standard TListBox control does, I need to use my own object based list that is streamable.
I don't want to use TCollection, DefineProperties sounds promising but I don't know how exactly I would implement this?
I would greatly appreciate some help with this please.
Thank you.
Override DefineProperties procedure in your TCustomListBox (let's name it TMyListBox here). In there it's possible to "register" as many fields as you wish, they will be stored in dfm in the same way as other fields, but you won't see them in object inspector. To be honest, I've never encountered having more then one property defined this way, called 'data' or 'strings'.
You can define 'normal' property or binary one. 'Normal' properties are quite handy for strings, integers, enumerations and so on. Here is how items with caption and ImageIndex can be implemented:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure ReadData(reader: TReader);
procedure WriteData(writer: TWriter);
protected
procedure DefineProperties(filer: TFiler); override;
//other stuff
public
//other stuff
property Items: TList read fItems; //not used for streaming, not shown in object inspector. Strictly for use in code itself. We can make it read-only to avoid memory leak.
published
//some properties
end;
that's DefineProperties implementation:
procedure TMyListBox.DefineProperties(filer: TFiler);
begin
filer.DefineProperty('data', ReadData, WriteData, items.Count>0);
end;
fourth argument, hasData is Boolean. When your component is saved to dfm, DefineProperties is called and it's possible to decide at that moment is there any data worth saving. If not, 'data' property is omitted. In this example, we won't have this property if there is no items present.
If we expect to ever use visual inheritance of this control (for example, create a frame with this listBox with predefined values and then eventually change them when put to form), there is a possibility to check, is value of this property any different than on our ancestor. Filer.Ancestor property is used for it. You can watch how it's done in TStrings:
procedure TStrings.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then
begin
Result := True;
if Filer.Ancestor is TStrings then
Result := not Equals(TStrings(Filer.Ancestor))
end
else Result := Count > 0;
end;
begin
Filer.DefineProperty('Strings', ReadData, WriteData, DoWrite);
end;
This would save a little bit of space (or lots of space if image is stored within) and sure is elegant, but in first implementation it can well be omitted.
Now the code for WriteData and ReadData. Writing is much easier usually and we may begin with it:
procedure TMyListBox.WriteData(writer: TWriter);
var i: Integer;
begin
writer.WriteListBegin; //in text dfm it will be '(' and new line
for i:=0 to items.Count-1 do begin
writer.WriteString(TListBoxItem(items[I]).caption);
writer.WriteInteger(TListBoxItem(items[I]).ImageIndex);
end;
writer.WriteListEnd;
end;
In dfm it will look like this:
object MyListBox1: TMyListBox
data = (
'item1'
-1
'item2'
-1
'item3'
0
'item4'
1)
end
Output from TCollection seems more elegant to me (triangular brackets and then items, one after another), but what we have here would suffice.
Now reading it:
procedure TMyListBox.ReadData(reader: TReader);
var item: TListBoxItem;
begin
reader.ReadListBegin;
while not reader.EndOfList do begin
item:=TListBoxItem.Create;
item.Caption:=reader.ReadString;
item.ImageIndex:=reader.ReadInteger;
items.Add(item); //maybe some other registering needed
end;
reader.ReadListEnd;
end;
That's it. In such a way rather complex structures can be streamed with ease, for example, two-dimensional arrays, we WriteListBegin when writing new row and then when writing new element.
Beware of WriteStr / ReadStr - these are some archaic procedures which exist for backward compatibility, ALWAYS use WriteString / ReadString instead!
Other way to do is to define binary property. That's used mostly for saving images into dfm. Let's say, for example, that listBox has hundreds of items and we'd like to compress data in it to reduce size of executable. Then:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure LoadFromStream(stream: TStream);
procedure SaveToStream(stream: TStream);
protected
procedure DefineProperties(filer: TFiler); override;
//etc
end;
procedure TMyListBox.DefineProperties(filer: TFiler);
filer.DefineBinaryProperty('data',LoadFromStream,SaveToStream,items.Count>0);
end;
procedure TMyListBox.SaveToStream(stream: TStream);
var gz: TCompressionStream;
i: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TCompressionStream.Create(stream);
try
value:=items.Count;
//write number of items at first
gz.Write(value, SizeOf(value));
//properties can't be passed here, only variables
for i:=0 to items.Count-1 do begin
item:=TListBoxItem(items[I]);
value:=Length(item.Caption);
//almost as in good ol' Pascal: length of string and then string itself
gz.Write(value,SizeOf(value));
gz.Write(item.Caption[1], SizeOf(Char)*value); //will work in old Delphi and new (Unicode) ones
value:=item.ImageIndex;
gz.Write(value,SizeOf(value));
end;
finally
gz.free;
end;
end;
procedure TMyListBox.LoadFromStream(stream: TStream);
var gz: TDecompressionStream;
i: Integer;
count: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TDecompressionStream.Create(stream);
try
gz.Read(count,SizeOf(count)); //number of items
for i:=0 to count-1 do begin
item:=TListBoxItem.Create;
gz.Read(value, SizeOf(value)); //length of string
SetLength(item.caption,value);
gz.Read(item.caption[1],SizeOf(char)*value); //we got our string
gz.Read(value, SizeOf(value)); //imageIndex
item.ImageIndex:=value;
items.Add(item); //some other initialization may be needed
end;
finally
gz.free;
end;
end;
In dfm it would look like this:
object MyListBox1: TMyListBox1
data = {
789C636260606005E24C86128654865C064386FF40802C62C40002009C5607CA}
end
78 is sort of signature of ZLib, 9C means default compression, so it works (there are only 2 items actually, not hundreds). Of course, this is just one example, with BinaryProperties any possible format may be used, for example saving to JSON and putting it into stream, or XML or something custom. But I'd not recommend to use binary unless it's absolutely inevitable, because it's difficult to see from dfm, what happens in component.
It seems like good idea to me to actively use streaming when implementing component: we can have no designer at all and set all values by manually editing dfm and see if component behaves correctly. Reading/loading itself can be tested easily: if component is loaded, then saved and text is just the same, it's all right. It's so 'transparent' when streaming format is 'human-readable', self-explaining that it often overweighs drawbacks (like file size) if there are any.
I want to create a record that I call TSprite at runtime.
TSprite is a image and 8 selection points that im using in a level editor I'm building.
type
TSprite = record
Image: TImage;
Selection: TSelection;
SelectionPointTL: TSelectionPoint; // top-left
SelectionPointTM: TSelectionPoint; // top-middle
SelectionPointTR: TSelectionPoint; // top-right
SelectionPointML: TSelectionPoint; // middle-left
SelectionPointMR: TSelectionPoint; // middle-right
SelectionPointBL: TSelectionPoint; // bottom-left
SelectionPointBM: TSelectionPoint; // bottom-middle
SelectionPointBR: TSelectionPoint; // bottom-right
end;
Now i want to store it in a array.
arrSprites: array[0..1000] of TSprite;
And now the creation (the part im struggling with)
This is what I have so far:
arrSprites[i].Image.Position.X := frmMainUI.CurrentMouseX;
arrSprites[i].Image.Position.Y := frmMainUI.CurrentMouseY;
arrSprites[i].Image.Bitmap.LoadFromFile('1.png');
arrSprites[i].Image.Visible := True;
arrSprites[i].Image.WrapMode := TImageWrapMode.iwStretch;
So what this code is supposed to do is to create an image with a selection around it inside a scrollbox i names : fsbcanvas.
Just to be clear I'm asking for the code that creates an instance of TSprite.
Thanks
Records don't need to be created. They are value types and you should think of them the same way that you think about other value types, e.g. Integer. Declare a local variable or a class field that is a value type and that's all you need to do. Similarly, a constant sized array is a value type.
So, the answer to your question, is that arrSprites does not need any special allocation. What does need allocation and initialization are the contents of your record. So if any of the fields in your record are class instances, then they need instantiating. So, consider this record:
type
TMyRecord = record
i: Integer;
obj: TObject;
end;
You can declare one like this:
var
rec: TMyRecord;
and the record itself is allocated. But you need to initialise its members:
rec.i := 42; // or some other initial value
rec.obj := TObject.Create; // instantiate the object
And when you are done with the record, you'd need to destroy the object.
rec.obj.Free;
This is easy to get wrong and so in general your records should contain only value types, or managed types (e.g. strings, interfaces, dynamic arrays etc.)
Now, I can't tell what your code is all about from what's in the question, but I suspect that your record has some class instances. Which immediately makes record a dubious choice of data structure. I'd hold these in a class which has constructor and destructor that manage the lifetime of the objects within.
And I'd also avoid using a constant length array. They are very inflexible. Instead I suggest you hold your sprite objects in a generic list, TList<T>. Or, perhaps even better, TObjectList<T> and that way you can let the list look after the life time of its members.
I was typing an answer as David Heffernan just beat me to it.
Still while at it i would like to add;
Realize that in your first code example you are using a record (TSprite) to hold an object (TImage). TImage is a visual component actually, but ultimately it descends from TObject.
A record is like most variables (such as Integer) in that it does not have to be instantiated and can be copied at will.
An object however is a pointer to an instance, and must be created/destroyed by following these steps
rec.Image := TImage.Create(nil);
// do other things..
rec.Image.Free;
And thus may result in memory leaks or Access Violation errors if not done properly. (for example while copying a TSprite..)
There is a lot of things that can go wrong in this setup, so therefore i say;
Using objects in records can get tricky. Consider keeping objects in objects:
A simple solution (if you indeed want to keep an instance or pointer reference of TImage inside a TSprite) would be to make TSprite also an object. It could keep track of of creating/destroying automatically by using its constructor and destructor:
TSpriteObject = class(TObject)
public
Image : TImage;
constructor Create;
destructor Destroy; override;
end;
and its implemenation:
constructor TSpriteObject.Create;
begin
Image := TImage.Create(nil);
// ^ TImage is a component and expects an Owner component that would also
// destroy it, so we use a nil value to disable that behavior.
end;
destructor TSpriteObject.Destroy;
begin
Image.Free;
end;
You can then have a TObjectList keep track of many instances of TSprite, and it'll destroy any TSprite (which destroys its TImage) when the list is cleared or destroyed.
(this is my first ever stackoverflow post attempt, please bear with me while i find out what i'm doing wrong here)
As David Heffernan said a class may be a better data structure for this situation. But if you decided to use records instead, you can declare methods in your record to refactor the instantiating and destroying parts:
type
TSprite = record
Image: TImage;
Selection: TSelection;
SelectionPointTL: TSelectionPoint; // top-left
SelectionPointTM: TSelectionPoint; // top-middle
SelectionPointTR: TSelectionPoint; // top-right
SelectionPointML: TSelectionPoint; // middle-left
SelectionPointMR: TSelectionPoint; // middle-right
SelectionPointBL: TSelectionPoint; // bottom-left
SelectionPointBM: TSelectionPoint; // bottom-middle
SelectionPointBR: TSelectionPoint; // bottom-right
procedure Create(aImageOwner: TComponent);
procedure Destroy;
end;
{ TSprite }
procedure TSprite.Create(aImageOwner: TComponent);
begin
Image := TImage.Create(aImageOwner);
end;
procedure TSprite.Destroy;
begin
Image.Free;
end;
// ...
var
Rec: TSprite;
begin
Rec.Create(Form1);
// ...
Rec.Destroy;
end;
Notice that it's not a real class instantiation so Rec := TSprite.Create(Form1); whould be meaningless, unless you want to define and implement TSprite.Create in that way.
Suppose my Delphi classes look like this:
interface
type
TMySubInfo = class
public
Name : string;
Date : TDateTime;
Age : Integer;
end;
TMyInfo = class
public
Name : string;
SubInfo : array of TMySubInfo;
destructor Destroy; override;
end;
implementation
destructor TMyInfo.Destroy;
begin
// hmmm..
end;
end.
To properly clean up, what should go in the destructor? Is it enough to do SetLength(SubInfo,0), or do I need to loop through and free each TMySubInfo? Do I need to do anything at all?
You need to loop through and free each created object.
You must know, that declaring a array of TMySubInfo doesn't actually create the objects. You have to create them later on.
I would use a TList instead for a more dynamic approach. You could even use a TObjectList that can free all its items when the list gets freed.
You should free each item, like this
destructor TMyInfo.Destroy;
var
I: Integer;
begin
for I:= Low(SubInfo) to High(SubInfo) do
SubInfo[I].Free;
SetLength(SubInfo, 0);
inherited;
end;
You free the objects the same way you allocated them. If you assigned an element's value by calling the constructor of a class, then free the object referenced by that element.
destructor TMyInfo.Destroy;
var
info: TMySubInfo;
begin
for info in SubInfo do
info.Free;
inherited;
end;
That uses syntax introduced in Delphi 2005. If you have an older version, use an explicit loop-control variable:
var
i: Integer;
begin
for i := 0 to High(SubInfo) do
SubInfo[i].Free;
You don't need to call SetLength at the end. A dynamic-array field like SubInfo gets released automatically when the object is destroyed. It works the same as interface, string, and Variant fields.
Agreed with all the above suggestions, but I want to add an (admittedly somewhat anal) recommendation that you always call the FreeAndNil() procedure in preference to the Free method.
Sooner or later you will accidentally access an object that you have already freed. If you have the habit of FreeAndNil-ing everything, then you get an immediate a/v on the line that contains the problem. If you merely Free'd the object, you will likely get a mysterious and apparently unconnected failure some time later...
This might seem obsessive in the context of a destructor, as here. Ok, it is a bit, but either one does it everywhere or not at all.
It is also anal, but Like to free in the reverse order to the creation, for example:
For I := List.Count-1 downto 0 do
List[I].Free;
I view creation and destruction as parethesis () although it makes litte actual execution diference.
Bri
If you created the objects via constructor calls, you need to make calls to Free to free them. If not, you don't.
For every new there should be a free.
Can you not use Finalize?