TObjectList<myclass> field into a class is not saved - delphi

I have a class that contains a TObjectList<T> list of objects of another class.
TMyElementClass = class (TPersistent)
private
....
public
....
end;
TMyElemContainerClass = class (TPersistent)
private
fElemList: TObjectList<TMyElementClass>;
...
published
ElemList: TObjectList<TMyElementClass> read fElemList write fElemList;
end;
var
Elements: TMyElemContainerClass;
I register both classes:
System.Classes.RegisterClass (TMyElemContainerClass);
System.Classes.RegisterClass (TMyElementClass);
The problem is, when the Elements object is "saved" to a stream, all of the published fields are correctly saved, but the list itself isn't.
What's wrong?

TObjectList is not a streamable class. Just because you use it in a published property doesn't mean the streaming system automatically knows how to stream it. If you are using the DFM streaming system, only classes that derive from TPersistent are streamable, but TObjectList does not. You will have to implement custom streaming logic for it.
Consider changing your design to use TCollection and TCollectionItem instead, eg:
TMyElementClass = class (TCollectionItem)
private
...
public
...
published
...
end;
TMyElemCollectionClass = class (TCollection)
private
function GetElem(Index: Integer): TMyElementClass;
procedure SetElem(Index: Integer; Value: TMyElementClass);
public
constructor Create; reintroduce;
function Add: TMyElementClass; reintroduce;
function Insert(Index: Integer): TMyElementClass; reintroduce;
property Elements[Index: Integer]: TMyElementClass read GetElem write SetElem; default;
end;
TMyElemContainerClass = class (TPersistent)
private
fElemList: TMyElemCollectionClass;
procedure SetElemList(Value: TMyElemCollectionClass);
...
public
constructor Create;
destructor Destroy; override;
...
published
ElemList: TMyElemCollectionClass read fElemList write SetElemList;
end;
...
constructor TMyElemCollectionClass.Create;
begin
inherited Create(TMyElementClass);
end;
function TMyElemCollectionClass.GetElem(Index: Integer): TMyElementClass;
begin
Result := TMyElementClass(inherited GetItem(Index));
end;
procedure TMyElemCollectionClass.SetElem(Index: Integer; Value: TMyElementClass);
begin
inherited SetItem(Index, Value);
end;
function TMyElemCollectionClass.Add: TMyElementClass;
begin
Result := TMyElementClass(inherited Add);
end;
function TMyElemCollectionClass.Insert(Index: Integer): TMyElementClass;
begin
Result := TMyElementClass(inherited Insert(Index));
end;
constructor TMyElemContainerClass.Create;
begin
inherited;
fElemList := TMyElemCollectionClass.Create;
end;
destructor TMyElemContainerClass.Destroy;
begin
fElemList.Destroy;
inherited;
end;
procedure TMyElemContainerClass.SetElemList(Value: TMyElemCollectionClass);
begin
fElemList.Assign(Value);
end;

Remy gave you one avenue to try.
Another possible way would be implement this list streaming manually.
You would have to
derive your TMyElemContainerClass from TComponent
remove ElemList from published properties
override DefineProperties method that would declare some virtual, not-existing published property to be streamed in and out. You may even name it ElemList or by any other identifier you would see fit. It would be used by Delphi instead your TObjectList object.
implement stream-oriented reader and writer methods for the said virtual property, they should iterate through all the items and save/load them.
Explore documentation from here: http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Classes.TComponent.DefineProperties
See one of many (and there really are many) examples (using arrays rather than lists, but the idea is the same) at How to use DefineProperties in a custom Class Object for Arrays - Delphi

Related

Delphi DefineProperties unpublished property DFM order

I'm developing a component for Query. It works like the "Properties" feature of DevExpress, but I need to place the order of the Unpublished Property I wrote to DFM with DefineProperties in the DFM file at the top of the TCollectionItem.
It works the same way in DevExpress. If you add a Field to the cxGrid and assign a value to the Properties property, you will see the value "PropertiesClassName" in the DFM file at the top.
When I open the DFM file and bring this Property to the top, the setter property of the "PropertiesClassName" Property works and I create that Class. It works seamlessly when reading data from the DFM stream. But no matter what I did I couldn't get the "PropertiesClassName" Property value to the top.
If you create a cxGrid on the form and add Field, and then take the "PropertiesClassName" property from DFM to the bottom of the DFM file, when you open the form again, you will see that it cannot find the relevant Class and an error occurs.
To change the DFM flow, I first assigned a value to the "PropertiesClassName" Property and then created the Class, but the problem was not solved. I did the opposite of this but the problem is still the same.
DFM Context
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
FieldName = 'TestField'
Properties.Convert2String = True
PropertiesClassName = 'TSearchBooleanProperties'
end>
DFM Context should be like
object QuerySearchEngine1: TQuerySearchEngine
SearchFields = <
item
PropertiesClassName = 'TSearchBooleanProperties'
FieldName = 'TestField'
Properties.Convert2String = True
end>
Classes
TSearchField = class(TCollectionItem)
private
FFieldName: string;
FProperties: TSearchFieldProperties;
FPropertiesClassName: string;
private
procedure SetFieldName(const Value: string);
procedure SetProperties(const Value: TSearchFieldProperties);
private
procedure ReaderProc(Reader: TReader);
procedure WriterProc(Writer: TWriter);
procedure SetPropertiesClassName(const Value: string);
protected
constructor Create(Collection: TCollection); override;
procedure DefineProperties(Filer: TFiler); override;
public
property PropertiesClassName: string read FPropertiesClassName write SetPropertiesClassName;
published
property FieldName: string read FFieldName write SetFieldName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
procedure TSearchField.SetPropertiesClassName(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesClassName(Value, Item) then
begin
if not Assigned(FProperties) or not (FProperties.ClassType = Item.ClassType) then
begin
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
FPropertiesClassName := Item.ClassType.ClassName;
FProperties := Item.ClassType.Create;
end;
end
else
begin
FPropertiesClassName := '';
if Assigned(FProperties) then
begin
FProperties.Free;
FProperties := nil;
end;
end;
end;
Property Editor
type
TSearchFieldPropertiesProperty = class(TClassProperty)
private
function GetInstance: TPersistent;
public
function GetAttributes: TPropertyAttributes; override;
procedure GetValues(Proc: TGetStrProc); override;
function GetValue: string; override;
procedure SetValue(const Value: string); override;
end;
function TSearchFieldPropertiesProperty.GetValue: string;
begin
for var I := 0 to Self.PropCount - 1 do
begin
var Inst := Self.GetComponent(I);
if Assigned(Inst) and Self.HasInstance(Inst) then
begin
if Inst is TSearchField then
begin
var PropInst := GetObjectProp(Inst, Self.GetPropInfo);
if Assigned(PropInst) then
begin
for var Item in SearchFieldPropertiesList do
begin
if PropInst.ClassType = Item.ClassType then
begin
Result := Item.Name;
Exit;
end;
end;
end;
end;
end;
end;
end;
procedure TSearchFieldPropertiesProperty.SetValue(const Value: string);
begin
var Item: TSearchFieldPropertiesItem;
if TryValidateSearchFieldPropertiesName(Value, Item) then
begin
var Inst := GetInstance;
if Assigned(Inst) then
begin
var Context := TRttiContext.Create;
var Rtype := Context.GetType(Inst.ClassType);
for var Prop in Rtype.GetProperties do
begin
if SameText(Prop.Name, 'PropertiesClassName') then
begin
Prop.SetValue(Inst, TValue.From<string>(Item.ClassType.ClassName));
Break;
end;
end;
end;
end;
end;
Pic for Design Time
The only problem is changing the order of the Property in that DFM flow.
Original answer at the bottom, here is a new suggestion:
We actually have something very similar in the JVCL where TJvHotTrackPersistent publishes a HotTrackOptions property.
This property is backed by an instance of TJvHotTrackOptions that gets derived in other classes that need specialized versions of it.
To tell the streaming subsystem to use the actual class found at streaming time, the constructor of that options class calls SetSubComponent(True); which places csSubComponent in the ComponentStyle property.
So what you should do is get rid of your DefineProperties, have TSearchFieldProperties inherit from TComponent and call SetSubComponent(True) in its constructor.
Then you create as many classes derived from TSearchFieldProperties as you need, each with its own set of published properties.
This means you should also get rid of the methods you showed in your submission.
In the end, you should have something along those lines:
type
TSearchFieldProperties = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
end;
TIntegerSearchFieldProperties = class(TSearchFieldProperties)
private
FIntValue: Integer;
published
property IntValue: Integer read FIntValue write FIntValue;
end;
constructor TSearchFieldProperties.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetSubComponent(True);
end;
With this you do not fight against the streaming system but rather work with it in the way it is meant to be used.
But if you stop there, you'll notice there is no way for you to specify the actual class name to be used for the TSearchFieldProperties instance used for the TSearchField.Properties property.
The only way to get the class name to be streamed before the subcomponent is streamed is to actually declare the class name as a published property, declared before the subcomponent like this:
type
TSearchField = class(TCollectionItem)
published
// DO NOT change the order of those two properties, PropertiesClassName must come BEFORE Properties for DFM streaming to work properly
property PropertiesClassName: string read GetPropertiesClassName write SetPropertiesClassName;
property Properties: TSearchFieldProperties read FProperties write SetProperties;
end;
function TSearchField.GetPropertiesClassName: string;
begin
Result := Properties.ClassName;
end;
procedure TSearchField.SetPropertiesClassName(const AValue: string);
begin
FProperties.Free; // no need to test for nil, Free already does it
FProperties := TSearchFieldPropertiesClass(FindClass(AValue)).Create(self);
end;
It might work if you just declare the published property like without creating a csSubComponent hierarchy but you'll most likely stumble on other hurdles along the way.
Note: this answer is wrong because DefineProperties is called last in TWriter.WriteProperties and so there is no way to change the order properties defined like this are written.
What if you change your DefineProperties override from this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
end;
to this:
procedure TSearchField.DefineProperties(Filer: TFiler);
begin
Filer.DefineProperty('PropertiesClassName', ReaderProc, WriterProc, FPropertiesClassName <> '');
inherited DefineProperties(Filer);
end;
Basically, call the inherited method AFTER you have defined your own property.
Note that I also specified which inherited method is called. I know it's not required, but it makes intent clearer and allows for Ctrl-Click navigation.

Dynamically created object (providing its classname as a string) do not call its constructor

Here is the object:
TCell = class(TPersistent)
private
FAlignmentInCell :byte;
public
constructor Create; virtual;
published
property AlignmentInCell:byte read FAlignmentInCell write FAlignmentInCell;
end;
this is its constructor:
constructor TCell.Create;
begin
inherited;
FAlignmentInCell:=5;
end;
Here is a function, which dynamically creates any object derived form TPersistent (parameter is class name provided as a string)
function CreateObjectFromClassName(AClassName:string):TPersistent;
var DynamicObject:TPersistent;
TempObject:TPersistent;
DynamicPersistent:TPersistent;
DynamicComponent:TComponent;
PersistentClass:TPersistentclass;
ComponentClass:TComponentClass;
begin
PersistentClass:=TPersistentclass(FindClass(AClassName));
TempObject:=PersistentClass.Create;
if TempObject is TComponent then
begin
ComponentClass:=TComponentClass(FindClass(AClassName));
DynamicObject:=ComponentClass.Create(nil);
end;
if not (TempObject is TComponent) then
begin
DynamicObject:=PersistentClass.Create; // object is really TCell, but appropriate constructor seems to be not called.
end;
result:=DynamicObject;
end;
My idea is to create new Cell (TCell) like this:
procedure TForm1.btn1Click(Sender: TObject);
var p:TPersistent;
begin
p := CreateObjectFromClassName('TCell');
ShowMessage(IntToStr(TCell(p).AlignmentInCell)); // it is 0. (Why?)
end;
When I want to check AlignmentInCell property I get 0, but I expected 5. Why? Is there way to fix it?
This is similar to a recent question.
You use TPersistentClass. But TPersistent does not have a virtual constructor, so the normal constructor for TPersistent is called, which is the constructor it inherits from TObject.
If you want to call the virtual constructor, you will have to declare a
type
TCellClass = class of TCell;
Now you can modify CreateObjectFromClassName to use this metaclass instead of TPersistenClass, and then the actual constructor will be called.
Also, TempObject is never freed. And instead of is, I would rather use InheritsFrom.
I did not test the following, but it should work:
function CreateObjectFromClassName(const AClassName: string; AOwner: TComponent): TPersistent;
var
PersistentClass: TPersistentclass;
begin
PersistentClass := FindClass(AClassName);
if PersistentClass.InheritsFrom(TComponent) then
Result := TComponentClass(PersistentClass).Create(AOwner)
else if PersistentClass.InheritsFrom(TCell) then
Result := TCellClass(PersistentClass).Create
else
Result := PersistentClass.Create;
end;
The compiler can't know for sure what value your variable of type TPersistentClass will hold at run time. So he assumes that it is exactly that: a TPersistentClass.
TPersistentClass is defined as a class of TPersistent. TPersistent has no virtual constructor, the compiler will therefore not include a call to dynamically look up the address of the constructor in the VMT of the actual class, but a 'hard-coded' call to the only matching constructor TPersistent has: the one it inherits from its base class TObject.
It might be a decision with reasons I don't know, but if you had chosen to define TCell as following
TCell = class(TComponent)
private
FAlignmentInCell: byte;
public
constructor Create(AOwner: TComponent); override;
published
property AlignmentInCell:byte read FAlignmentInCell write FAlignmentInCell;
end;
you wouldn't need TempObject and all the decision making in your CreateObjectFromClassName function (and the possible leaks as pointed out by others):
function CreateObjectFromClassName(AClassName:string): TComponent;
var
ComponentClass:TComponentClass;
begin
ComponentClass:=TComponentClass(FindClass(AClassName));
Result := ComponentClass.Create(nil);
end;
And make sure to manage the Results life-time as it has no Owner.

Delphi: Using function from main class in a subclass

On the new side of writing classes and have a little problem which I have tried researching but still no answer.
I want to create one instance of a class which creates multiple subclasses which creates subclasses of their own. The idea is to use code like this in main program:
procedure TForm1.Button1Click(Sender: TObject);
var
Temp : Integer;
begin
MainClass := TMainClass.Create(Form1);
Temp := MainClass.SubClass1.SubSubClass1.SomeValue;
end;
The main class looks like this and is created in seperate file:
TMainClass = class(TObject)
private
FSubClass1 : TSubClass1;
public
ValueFromAnySubClass : Integer;
property SubClass1 : TSubClass1 read FSubClass1 write FSubClass1;
procedure SetSomeValueFromMainClass(Value : Integer);
end;
...
...
...
procedure TMainClass.SetSomeValueFromMainClass(Value : Integer);
begin
ValueFromAnySubClass := Value;
end;
The sub class also in seperate file:
TSubClass1 = class(TObject)
private
FSubSubClass1 : TSubSubClass1;
public
property SubSubClass1 : TSubSubClass1 read FSubSubClass1 write FSubSubClass1;
end;
And now for the sub sub class also in seperate file:
TSubSubClass1 = class(TObject)
private
SomeValue : Integer;
function GetSomeValue : Integer;
procedure SetSomeValue(Value : Integer);
public
property SomeValue : Integer read GetSomeValue write SetSomeValue;
end;
...
...
...
procedure TSubSubClass1.SetSomeValue(Value : Integer);
begin
SetSomeValueFromMainClass(Value); <<< Error Here <<<
end;
How do I get to use the functions and procedures from the main class in my sub classes?
You don't need a subclass to use a function from another class. Also your sample code has not used subclasses at all. A proper subclass automatically has access to all public and proteced functions of its ancestors.
As David has already pointed out, there are serious flaws in your intended deisgn.
Furthermore, based on your comment:
The classes all perform vastly different functions but need to write data to a piece of hardware at the end of the day. The data is read from the hardware and kept in memory to work with until its written back to the hardware component once all work is completed. The procedure in the main class takes care of writing real time data to the hardware whenever it is required by any of the subclasses.
to David's answer: you don't need subclasses at all.
All you need is a public method on your hardware class. And for each instance of your other classes to have a reference to the correct instance of your hardware class.
type
THardwareDevice = class(TObject)
public
procedure WriteData(...);
end;
TOtherClass1 = class(TObject)
private
FDevice: THardwareDevice;
public
constructor Create(ADevice: THardwareDevice);
procedure DoSomething;
end;
constructor TOtherClass1.Create(ADevice: THardwareDevice);
begin
FDevice := ADevice;
end;
procedure TOtherClass1.DoSomething;
begin
//Do stuff, and maybe you need to tell the hardware to write data
FDevice.WriteData(...);
end;
//Now given the above you can get two distinct object instances to interact
//as follows. The idea can be extended to more "other class" types and instances.
begin
FPrimaryDevice := THardwareDevice.Create(...);
FObject1 := TOtherClass1.Create(FPrimaryDevice);
FObject1.DoSomething;
//NOTE: This approach allows extreme flexibility because you can easily
// reference different instances (objects) of the same hardware class.
FBackupDevice := THardwareDevice.Create(...);
FObject2 := TOtherClass1.Create(FBackupDevice);
FObject2.DoSomething;
...
end;
The design looks really poor. You surely don't want to have all these classes knowing all about each other.
And any time you see a line of code with more than one . operator you should ask yourself if the code is in the right class. Usually that indicates that the line of code that has multiple uses of . should be in one of the classes further down the chain.
However, if you want to call a method, you need an instance. You write:
procedure TSubSubClass1.SetSomeValue(Value : Integer);
begin
SetSomeValueFromMainClass(Value);
end;
And naturally this does not compile. Because SetSomeValueFromMainClass is not a method of TSubSubClass1. Rather SetSomeValueFromMainClass is a method of TMainClass. So, to call that method, you need an instance of TMainClass.
Which suggests that, if you really must do this, that you need to supply to each instance of TSubSubClass1 an instance of TMainClass. You might supply that in the constructor and make a note of the reference.
Of course, when you do this you now find that your classes are all coupled together with each other. At which point one might wonder whether or not they should be merged.
I'm not saying that merging these classes is the right design. I would not like to make any confident statement as to what the right design is. Perhaps what you need is an interface that promises to implement the setter as a means to decouple things. All I am really confident in saying is that your current design is not the right design.
As far as I know Subclass word is usually using in inheritance concept but the code you wrote are some compound classes. As you may see the constructor of many classes in Delphi have an argument which named AOwner that may be TComponent or TObject or ...
If you define the constructors of your TSubclass1 and TSubSubClass1 like as follow and Change the Owner of classes that you defined as properties to Self in set functions you may access to your TMainClass by typecasting the Owner property.
I changed your code a little to just work as you want, but I suggest change your design.
TSubSubClass1 = class(TObject)
private
FOwner: TObject;
function GetSomeValue:Integer;
procedure SetSomeValue(const Value: Integer);
procedure SetOwner(const Value: TObject);
public
constructor Create(AOwner:TObject);reintroduce;
property Owner:TObject read FOwner write SetOwner;
property SomeValue : Integer read GetSomeValue write SetSomeValue;
end;
TSubClass1 = class(TObject)
private
FSubSubClass1: TSubSubClass1;
FOwner:TObject;
procedure SetSubSubClass1(const Value: TSubSubClass1);
procedure SetOwner(const Value: TObject);
public
constructor Create(AOwner:TObject);reintroduce;
property Owner:TObject read FOwner write SetOwner;
property SubSubClass1 : TSubSubClass1 read FSubSubClass1 write SetSubSubClass1;
end;
TMainClass = class(TObject)
private
FSubClass1: TSubClass1;
procedure SetSubClass1(const Value: TSubClass1);
public
ValueFromAnySubClass : Integer;
constructor Create;
property SubClass1 : TSubClass1 read FSubClass1 write SetSubClass1;
procedure SetSomeValueFromMainClass(Value : Integer);
end;
implementation
{ TSubSubClass1 }
constructor TSubSubClass1.Create(AOwner: TObject);
begin
Owner:=AOwner;
end;
function TSubSubClass1.GetSomeValue: Integer;
begin
Result:=TMainClass(TSubClass1(Self.Owner).Owner).ValueFromAnySubClass;
end;
procedure TSubSubClass1.SetOwner(const Value: TObject);
begin
FOwner := Value;
end;
procedure TSubSubClass1.SetSomeValue(const Value: Integer);
begin
TMainClass(TSubClass1(Self.Owner).Owner).SetSomeValueFromMainClass(Value);
end;
{ TSubClass1 }
constructor TSubClass1.Create(AOwner: TObject);
begin
Owner:=AOwner;
FSubSubClass1:=TSubSubClass1.Create(Self);
end;
procedure TSubClass1.SetOwner(const Value: TObject);
begin
FOwner := Value;
end;
procedure TSubClass1.SetSubSubClass1(const Value: TSubSubClass1);
begin
FSubSubClass1 := Value;
FSubSubClass1.Owner:=Self;
end;
{ TMainClass }
constructor TMainClass.Create;
begin
FSubClass1:=TSubClass1.Create(Self);
end;
procedure TMainClass.SetSomeValueFromMainClass(Value: Integer);
begin
ValueFromAnySubClass := Value;
end;
procedure TMainClass.SetSubClass1(const Value: TSubClass1);
begin
FSubClass1 := Value;
FSubClass1.Owner:=Self;
end;
you must put the proper filename in uses part of implementation.

Can I declare TCriticalSection object as a public class field?

Can I have a TCriticalSection object declared as a public field, such as:
type
TMyObject = class
public
CS: TCriticalSection;
end;
I would like to make that field public to allow any thread enter and leave the critical section object, which internally protects integrity of the TMyObject instance. So, can I declare TCriticalSection object as a public class field ?
The simple answer is yes. A field of a class is a perfectly normal place to hold a TCriticalSection.
In order for a critical section to be able to serialize access to a shared resource, all threads must refer to the same instance of the critical section. So, from that fact you conclude that you need to make sure that all threads refer to the same instance of the class. Then when they read the critical section field of that class, all threads are accessing the same instance of the critical section.
It is usually considered to be bad practice to declare public fields. You would normally expose such a thing through either a property, or methods of the class.
This is what I have done in the past to achieve this:
TMyObject = class(TObject)
private
FCS :TCriticalSection;
FMyProp :string;
function GetMyProp :string;
procedure SetMyProp( const NewValue :string );
protected
public
constructor Create;
destructor Destroy; override;
// properties
property MyProp :string read GetMyProp write SetMyProp;
end;
followed by the implementation:
constructor TMyObject.Create;
begin
inherited Create;
FCS := TCriticalSection.Create;
FMyProp := '';
end;
destructor TMyObject.Destroy;
begin
FCS.Free;
inherited Destroy;
end;
function TMyObject.GetMyProp :string;
begin
FCS.Enter;
try
result := FMyProp;
finally
FCS.Leave;
end;
end;
procedure TMyObject.SetMyProp( const NewValue :string );
begin
FCS.Enter;
try
FMyProp := NewValue;
finally
FCS.Leave;
end;
end;
Note that I haven't exposed the FCS field.

Referencing a class inside another class by address

I have the following problem: I've got an entity class 'TEntity' and a mesh class 'TMesh' and TEntity needs to know when its element, (TMesh) is removed. Is there a possible working way I can call the TEntity method 'OnMeshRemove' from the TMesh destructor?
//uTEntity
interface
uses
uTMesh;
type
TEntity = class
Mesh : TMesh;
constructor Create(); overload;
procedure OnMeshRemove();
end;
implementation
constructor TEntity.Create();
begin
Mesh := TMesh.Create();
Mesh.EntityContainer := #self;
end;
procedure TEntity.OnMeshRemove();
begin
//Do stuff
end;
end.
//uTMesh
interface
type
TMesh = class
EntityContainer : Pointer;
destructor Remove();
end;
implementation
uses
uTEntity;
destructor TMesh.Remove();
var
PEntity : ^TEntity;
begin
PEntity := EntityContainer;
if Assigned( PEntity^ ) then
begin
PEntity^.OnMeshRemove();
end;
inherited Destroy();
end;
Example:
var
Ent : TEntity;
begin
Ent := TEntity.Create();
Ent.Mesh.Remove();
//I want Ent.OnMeshRemove to be called. In my example code, there is a pointer problem. I need to solve that. Thanks!
end;
PS: I don't want to have a TEntity procedure like TEntity.RemoveMesh();
All objects in Delphi are pointer type so no need to deference it. Here is a bit more simpler
type
TEntity = class
public
Mesh: TMesh;
constructor Create;
destructor Destroy; override;
end;
implementation
constructor TEntity.Create;
begin
inherited Create;
Mesh := TMesh.Create;
Mesh.EntityContainer := Self;
end;
procedure TEntity.Destroy;
begin
if Mesh <> nil then
begin
Mesh.EntityContainer := nil;
FreeAndNil(Mesh);
end;
inherited Destroy;
end;
//***************************************************
type
TMesh = class
EntityContainer: TObject;
destructor Destroy; override;
end;
implementation
uses
uTEntity;
destructor TMesh.Destroy;
begin
if (EntityContainer <> nil) and (TEntity(EntityContainer).Mesh = Self) then
TEntity(EntityContainer).Mesh := nil;
EntityContainer := nil;
inherited Destroy;
end;
Your TEntity instance should register itself with TMesh instance so that when TMesh instance is being freed, it will modify the TEntity instance about it.
If your classes are derived from TComponent class, then this mechanism is already implemented for you; each TComponent instance has a method called FreeNotification and a method called Notification. Any TComponent instance can register itself with the other component calling its FreeNotification method and passing itself as the parameter. Whenever a component is being destroyed, it will check the list of components registered for its free notification, and will invoke Notification method of each registered component. This way, the register component will be notified whenever the other component is about to be destroyed.
If one TComponent instance is the owner of the other (In your case, TEntity can be the owner of TMesh instance), then it will be notified automatically whenever TMesh instance is removed. All you need to do is to override its Notification method and do whatever you want to do inside that method.
If you do not want to derive your classes from TComponent class or for any reason do not want to use Delphi's implementation, you can implement the same behavior in your own classes. You need an internal list in your TMesh class which holds a list of classes which should be notified. You also need a method to register a class with your TMesh class, and eventually you need a method in your TEntity class which should be called by TMesh whenever it is being freed.
Here is a simple source code just for demonstrating the general idea. Take note that this sample code is not thread-safe, and might lack some other checks. I just wrote it fast to show you how to implement such an idea:
unit Unit1;
interface
uses
Classes, Generics.Collections;
type
TBaseClass = class
private
FNotificationList : TList<TBaseClass>;
protected
procedure Notify(AClass: TBaseClass); virtual;
public
procedure RegisterForNotification(AClass: TBaseClass);
procedure UnregisterNotification(AClass: TBaseClass);
constructor Create;
destructor Destroy; override;
end;
TMesh = class(TBaseClass)
end;
TEntity = class(TBaseClass)
private
FMesh : TMesh;
FOnMeshRemoved : TNotifyEvent;
procedure SetMesh(Value: TMesh);
protected
procedure Notify(AClass: TBaseClass); override;
procedure DoMeshRemoved; virtual;
public
constructor Create;
destructor Destroy; override;
property Mesh : TMesh read FMesh write SetMesh;
property OnMeshRemoved : TNotifyEvent read FOnMeshRemoved write FOnMeshRemoved;
end;
implementation
{ TBaseClass }
constructor TBaseClass.Create;
begin
inherited;
FNotificationList := TList<TBaseClass>.Create;
end;
destructor TBaseClass.Destroy;
var
AClass: TBaseClass;
begin
if Assigned(FNotificationList) then
begin
if (FNotificationList.Count > 0) then
for AClass in FNotificationList do
AClass.Notify(Self);
FNotificationList.Free;
FNotificationList := nil;
end;
inherited;
end;
procedure TBaseClass.Notify(AClass: TBaseClass);
begin
end;
procedure TBaseClass.RegisterForNotification(AClass: TBaseClass);
begin
if not Assigned(AClass) then
Exit;
if FNotificationList.IndexOf(AClass) < 0 then
FNotificationList.Add(AClass);
end;
procedure TBaseClass.UnregisterNotification(AClass: TBaseClass);
begin
if not Assigned(AClass) then
Exit;
if FNotificationList.IndexOf(AClass) >= 0 then
FNotificationList.Remove(AClass);
end;
{ TEntity }
constructor TEntity.Create;
begin
inherited;
end;
destructor TEntity.Destroy;
begin
if Assigned(FMesh) then
FMesh.UnregisterNotification(Self);
inherited;
end;
procedure TEntity.DoMeshRemoved;
begin
if Assigned(FOnMeshRemoved) then
FOnMeshRemoved(Self);
end;
procedure TEntity.Notify(AClass: TBaseClass);
begin
inherited;
FMesh := nil;
DoMeshRemoved;
end;
procedure TEntity.SetMesh(Value: TMesh);
begin
if Assigned(FMesh) then
begin
FMesh.UnregisterNotification(Self);
FMesh := nil;
end;
if Assigned(Value) then
begin
FMesh := Value;
FMesh.RegisterForNotification(Self);
end;
end;
end.
In this code, both TEntity and TMesh are derived from TBaseClass which provides notification mechanism. TEntity does not create any instance of TMesh initially, but you can assign a created TMesh instance to its Mesh property. Doing so will make TEntity to assign that value to its FMesh field, and call its RegisterForNotification class so that it can be notified if the mesh is being destroyed.
When the mesh is being destroyed, it iterates over all the objects registered themselves with it, and invokes their Notify method. Here it would be Notify method of TEntity class. Inside Notify method of TEntity, it first makes its FMesh field nil, because that object is being destroyed and there is no need to keep a reference of a dead object. It then calls DoMeshRemove method which is an event-invoker for OnMeshRemove event.
Edit: back behind a PC.
The classic way of maintaining lists of predetermined objects of a certain class is using TCollection/TCollectionItem.
TCollection/TCollectionItem are heavily used in Delphi (see this list).
They are lighter weight than TComponent (that automatically maintains Owner/Components/ComponentCount and has FreeNotification), as TCollectionItem and TCollection both descend from the TPersistent branch in stead of TComponent branch.
TCollection has a nice virtual Notify method:
procedure TCollection.Notify(Item: TCollectionItem; Action: TCollectionNotification);
begin
case Action of
cnAdded: Added(Item);
cnDeleting: Deleting(Item);
end;
end;
From Delphi 2009, you have generics, so then it can pay off big time to use TList (in your cast TList<TEntity>, as it contains this very nice Notify method and OnNotify event:
procedure TList<T>.Notify(const Item: T; Action: TCollectionNotification);
begin
if Assigned(FOnNotify) then
FOnNotify(Self, Item, Action);
end;
These two solutions work well if your TMesh is indeed a collection/list of TEntity.
If it TMesh is a non-list graph of TEntity, then it is better to descend both from TComponent like vcldeveloperlink text explained in his answer.
Andy Bulka has a nice blog post on the various ways of using lists and collections, including a well balanced view of TCollection/TCollectionItem usage.
--jeroen
Old answer (great iPad auto-complete bugs fixed):
Sorry for the short answer, as I am on the road only carrying a mobile device.
It looks like your mesh is a container for entities.
If so, then you should look into TCollection and TCollectionItem.
Derive your mesh from the former and your entity from the latter.
The delphi vcl source code contain many examples of these: fields/field or actions/action are good starters.

Resources