Component property derived from a custom class - delphi

I create my own class and I want to use it in my new component but I am getting an error...
The code is the following:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
public
procedure SetMyClass(aName: string; aNumber: double);
published
property MyClass: TMyClass write SetMyClass;
end;
procedure SetMyClass(aName: string; aNumber: double);
begin
FMyClass.Name:= aName;
FMyClass.Number:= aNumber;
end;
it appears that the property has incompatible types, I don't know why.
Does anybody has a clue about that and how can I solve this problem.
Having a FName and FNumber as fields in TMyComponent is not an option, my code is more complex and this is a simple example to explain my goal.
thanks

The things that I can see wrong with your code at present are:
The property setter must receive a single parameter of the same type as the property, namely TMyClass.
The property setter must be a member of the class, but you've implemented it as a standalone procedure.
A published property needs to have a getter.
So the code would become:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
procedure TMyComponent.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
This code does not instantiate FMyClass. I'm guessing that the code that does instantiate FMyClass is part of the larger component code that has been excised for the sake of this question. But obviously you do need to instantiate FMyClass.
An alternative to instantiating FMyClass is to turn TMyClass into a record. Whether or not that would suit your needs I cannot tell.
It looks like you are having some problems instantiating this object. Do it like this:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
procedure SetMyClass(Value: TMyClass);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FMyClass:= TMyClass.Create;
end;
destructor TMyComponent.Destroy;
begin
FMyClass.Free;
inherited;
end;
procedure TMyComponent.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
One final comment. Using MyClass for an object is a bad name. Use class for the type, and object for the instance. So, your property should be MyObject and the member field should be FMyObject etc.

Try this:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
public
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass write SetMyClass;
end;
procedure TMyComponent.SetMyClass(Value);
begin
FMyClass := Value;
end;

unit MyComponentTest2;
interface
uses SysUtils, Classes, Controls, Forms, ExtCtrls, Messages, Dialogs;
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponentTest2 = class(TCustomPanel)
private
FMyClass: TMyClass;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
procedure Register;
implementation
constructor TMyComponentTest2.Create(AOwner: TComponent);
begin
Inherited Create(AOwner);
FMyClass:= TMyClass.Create;
end;
destructor TMyComponentTest2.Destroy;
begin
Inherited;
FMyClass.Free;
end;
procedure TMyComponentTest2.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
procedure Register;
begin
RegisterComponents('MyComponents', [TMyComponentTest2]);
end;
end.

Related

Component with TStrings property has "Code Editor" disabled in String List Editor

I have written a Delphi component that has a property of type TStrings. All works well except that when the String List Editor is launched, the "Code Editor" button is disabled. Anyone know what I need to set to allow this?
Perhaps this is due to being called from the collection editor?
The entire component is is about 80 lines so I put it all here. It is a VCL component.
// Simple example of of creating a OwnedCollection of TStrings
unit TextStorageMin;
interface
uses
System.Classes, System.SysUtils, Winapi.Windows, System.Generics.Collections;
type
// Storage class to store TStrings
TStorageStrings = class(TCollectionItem)
private
FStrings: TStrings;
procedure SetStrings(const Value: TStrings);
public
published
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
// Why, when this is brought up in the Strings List Editor, is
// the "Code Editor" not enabled.
property Strings: TStrings read FStrings write SetStrings;
end;
// Just simple Owned Collection
TStorageList = class(TOwnedCollection);
// This our component.
TTextStorageMin = class(TComponent)
private
FStorageList: TStorageList;
public
published
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Items: TStorageList read FStorageList write FStorageList;
end;
procedure Register;
implementation
// Register it
procedure Register;
begin
RegisterComponents('CompDev', [TTextStorageMin]);
end;
{ TTextStorage }
constructor TTextStorageMin.Create(AOwner: TComponent);
begin
inherited;
FStorageList := TStorageList.Create(AOwner, TStorageStrings);
end;
destructor TTextStorageMin.Destroy;
begin
FStorageList.Free;
inherited;
end;
{ TStorageStrings }
constructor TStorageStrings.Create(Collection: TCollection);
begin
inherited;
FStrings := TStringList.Create;
end;
destructor TStorageStrings.Destroy;
begin
FStrings.Free;
inherited;
end;
procedure TStorageStrings.SetStrings(const Value: TStrings);
begin
FStrings.Assign(Value);
end;
Your main component is coded all wrong. It is completely mismanaging the ownership of the TStorageList object. It is assigning the wrong Owner to the object, and there is no property setter implementee to avoid a memory leak and taking ownership of an external object (in this case, one created and destroyed by the IDE at design-time).
Also, your TStorageStrings class is missing an overload of Assign() (or AssignTo()), which also plays into the above mismanagement.
The code should look more like this instead:
// Simple example of of creating a OwnedCollection of TStrings
unit TextStorageMin;
interface
uses
System.Classes;
type
// Storage class to store TStrings
TStorageStrings = class(TCollectionItem)
private
FStrings: TStrings;
procedure SetStrings(const Value: TStrings);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
procedure Assign(ASource: TPersistent); override;
published
property Strings: TStrings read FStrings write SetStrings;
end;
// Just simple Owned Collection
TStorageList = class(TOwnedCollection);
// This our component.
TTextStorageMin = class(TComponent)
private
FStorageList: TStorageList;
procedure SetItems(const Value: TStorageList);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Items: TStorageList read FStorageList write SetItems;
end;
procedure Register;
implementation
// Register it
procedure Register;
begin
RegisterComponents('CompDev', [TTextStorageMin]);
end;
{ TTextStorage }
constructor TTextStorageMin.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FStorageList := TStorageList.Create(Self, TStorageStrings);
end;
destructor TTextStorageMin.Destroy;
begin
FStorageList.Free;
inherited;
end;
procedure TTextStorageMin.SetItems(const Value: TStorageList);
begin
FStorageList.Assign(Value);
end;
{ TStorageStrings }
constructor TStorageStrings.Create(Collection: TCollection);
begin
inherited Create(Collection);
FStrings := TStringList.Create;
end;
destructor TStorageStrings.Destroy;
begin
FStrings.Free;
inherited;
end;
procedure TStorageStrings.Assign(ASource: TPersistent);
begin
if ASource is TStorageStrings then
FStrings.Assign(TStorageStrings(ASource).Strings)
else
inherited;
end;
procedure TStorageStrings.SetStrings(const Value: TStrings);
begin
FStrings.Assign(Value);
end;
end.

Can constructors be private? [duplicate]

Take a look at this class:
TTest = class(TObject)
public
constructor Create(A:Integer);overload;
constructor Create(A,B:Integer);overload;
end;
Now when we want to use the class:
var
test: TTest;
begin
test:= TTest.Create; //this constructor is still visible and usable!
end;
Can anyone help me with hiding this constructor?
So long as you have overloaded constructors named Create, you cannot hide the parameterless TObject constructor when deriving from TObject.
This is discussed here: http://www.yanniel.info/2011/08/hide-tobject-create-constructor-delphi.html
If you are prepared to put another class between your class and TObject you can use Andy Hausladen's trick:
TNoParameterlessContructorObject = class(TObject)
strict private
constructor Create;
end;
TTest = class(TNoParameterlessContructorObject)
public
constructor Create(A:Integer);overload;
constructor Create(A,B:Integer);overload;
end;
You can hide the inherited Create by just introducing a non overloaded Create. As you need two overloaded Create, you can either merge those into one Create with an optional second parameter:
TTest = class(TObject)
public
constructor Create(A:Integer; B: Integer = 0);
end;
This will give a compiler warning, signalling that you're hiding the default parameterless constructor. To get rid of the warning you can declare the hiding constructor like so:
TTest = class(TObject)
public
constructor Create(A:Integer; B: Integer = 0); reintroduce;
end;
or, if this is not feasible, you can introduce an intermediate class introducing the first create and then the final class with the overloaded second one:
preTest = class(TObject)
public
constructor Create(A:Integer); reintroduce;
end;
TTest = class(preTest)
public
constructor Create(A,B:Integer);overload;
end;
Another option is to use the deprecated keyword and raise an exception at runtime.
TTest = class(TObject)
public
constructor Create; overload; deprecated 'Parameterless constructor is not Supported for a TTest class';
constructor Create(const A: Integer); overload;
constructor Create(const A, B: Integer); overload;
end;
implementation
constructor TTest.Create;
begin
raise Exception.Create('Parameterless constructor is not Supported for a TTest class.');
end;
Through the two inheritance, user creation of TMySingleton class can be prevented from design time rather than runtime.
unit MySingleton;
interface
uses System.Classes, System.SysUtils;
type
// Constructor Block external access
THideConstructor = class abstract
strict protected
constructor Create; virtual; abstract;
end;
// Switching the access to the Create function THideConstructor in TObject through the constructor Overloading
// Declaring Create Method as a procedure to prevent class call-TMySingle.Create('string') call impossible
TOverloadConstructor = class(THideConstructor)
public
procedure Create(s: string); reintroduce; overload; deprecated 'null method';
end;
TMySingleton = class sealed(TOverloadConstructor)
private
class var MyObj: TMySingleton;
strict protected
// Hiding TOverloadConstructor.Create(s: string);
// Implement THideConstructor.Create
constructor Create; override;
public
class function Obj: TMySingleton;
function Echo(const value: string): String;
destructor Destroy; override;
end;
implementation
{ TMySingleton }
constructor TMySingleton.Create;
begin
// TODO
end;
destructor TMySingleton.Destroy;
begin
Self.MyObj := nil;
inherited;
end;
function TMySingleton.Echo(const value: string): String;
begin
result := value;
end;
class function TMySingleton.Obj: TMySingleton;
begin
if MyObj = nil then
MyObj := Self.Create;
result := MyObj;
end;
{ TOverloadContructor }
procedure TOverloadConstructor.Create(s: string);
begin
// null method
end;
initialization
TMySingleton.MyObj := nil;
finalization
if Assigned(TMySingleton.MyObj) then
FreeAndNil(TMySingleton.MyObj);
end.
If the user
var
Singleton: TMySingleton;
begin
Singleton := TMySingleton.Create;
Design-time error occurs.
[dcc32 Error] Unit1.pas(33): E2625 Private member 'THideConstructor.Create' is inaccessible here MySingleton.pas(11): Related method: constructor Create;
enter image description here
Also, you can't see any autocomplete hints named Create.

Factory pattern, initialization parameters

I am reading Hodges book "More Coding in Delphi", section on Factory Pattern. I come up with a problem. I need to implement Init procedures for each descendant of TBaseGateway class. The problem is I do not know how to pass correct record type. Is there any nice solution?
unit Unit2;
interface
uses
Generics.Collections, System.SysUtils, System.Classes, Dialogs;
type
TGatewayTpe = (gtSwedbank, gtDNB);
type
IGateway = interface
['{07472665-54F5-4868-B4A7-D68134B9770B}']
procedure Send(const AFilesToSend: TStringList);
end;
type
TBaseGateway = class(TAggregatedObject, IGateway)
public
procedure Send(const AFilesToSend: TStringList); virtual; abstract;
end;
type
TSwedbankGateway = class(TBaseGateway)
public
// procedure Init(const ASwedbanRecord: TSwedBankRecord);
procedure Send(const AFilesToSend: TStringList); override;
end;
type
TDNBGateway = class(TBaseGateway)
public
// procedure Init(const ADNBRecord: TDNBRecord);
procedure Send(const AFilesToSend: TStringList); override;
end;
type
TGatewayFunction = reference to function: TBaseGateway;
type
TGatewayTypeAndFunction = record
GatewayType: TGatewayTpe;
GatewayFunction: TGatewayFunction;
end;
type
TGatewayFactory = class
strict private
class var FGatewayTypeAndFunctionList: TList<TGatewayTypeAndFunction>;
public
class constructor Create;
class destructor Destroy;
class procedure AddGateway(const AGatewayType: TGatewayTpe;
const AGatewayFunction: TGatewayFunction);
end;
implementation
class procedure TGatewayFactory.AddGateway(const AGatewayType: TGatewayTpe;
const AGatewayFunction: TGatewayFunction);
var
_GatewayTypeAndFunction: TGatewayTypeAndFunction;
begin
_GatewayTypeAndFunction.GatewayType := AGatewayType;
_GatewayTypeAndFunction.GatewayFunction := AGatewayFunction;
FGatewayTypeAndFunctionList.Add(_GatewayTypeAndFunction);
end;
class constructor TGatewayFactory.Create;
begin
FGatewayTypeAndFunctionList := TList<TGatewayTypeAndFunction>.Create;
end;
class destructor TGatewayFactory.Destroy;
begin
FreeAndNil(FGatewayTypeAndFunctionList);
end;
procedure TSwedbankGateway.Send(const AFilesToSend: TStringList);
begin
ShowMessage(Self.ClassName);
end;
procedure TDNBGateway.Send(const AFilesToSend: TStringList);
begin
ShowMessage(Self.ClassName);
end;
initialization
TGatewayFactory.AddGateway(gtDNB,
function: TBaseGateway
begin
Result := TDNBGateway.Create(nil);
end);
TGatewayFactory.AddGateway(gtSwedbank,
function: TBaseGateway
begin
Result := TSwedbankGateway.Create(nil);
end);
end.

Collection editor does not open for a TCollection property in a TPersistent property

I've got my custom collection property which is working great when it is a direct member of my component.
But I want to move the collection property to a TPersistent propery within my component. And now comes the problem, it doesn't work: double clicking on the collection property in the object inspector normally opens the collection editor, but it does not anymore.
Fist of all - what should I pass to the contructor of the TPersistent property?
TMyCollection = class(TCollection)
constructor Create(AOwner: TComponent); // TMyCollection constuctor
...
I can't pass Self, so should I pass my persistent owner?
constructor TMyPersistent.Create(AOwner: TComponent);
begin
inherited Create;
fOwner := AOwner;
fMyCollection := TMyCollection.Create(AOwner); // hmmm... doesn't make sense
end;
I think I'm missing something. If more code is needed just please comment this post.
A TCollection's constructor does not need a TComponent, but a TCollectionItemClass.
Your collection now being a member of a TPersistent property instead of being a direct member of the component makes no difference for the constructor.
Update
What dóes differ is the ownership, but then at the TPersistent level, which should be managed by a correct implementation of GetOwner:
GetOwner returns the owner of an object. GetOwner is used by the GetNamePath method to find the owner of a persistent object. GetNamePath and GetOwner are introduced in TPersistent so descendants such as collections can appear in the Object Inspector.
You have to tell the IDE that your TCollection property is owned by the TPersistent property, which in turn is owned by the component.
The tutorial you are using has several errors regarding this implementation:
The owner of the collection is declared as TComponent, which should be TPersistent,
GetOwner is not implemented for the TPersistent property class, and
The fix shown at the end of the tutorial, stating that the TPersistent property should inherit from TComponent instead, is plain wrong; or more nicely said: is rather a workaround for not implementing GetOwner.
This is how it should look like:
unit MyComponent;
interface
uses
Classes, SysUtils;
type
TMyCollectionItem = class(TCollectionItem)
private
FStringProp: String;
protected
function GetDisplayName: String; override;
public
procedure Assign(Source: TPersistent); override;
published
property StringProp: String read FStringProp write FStringProp;
end;
TMyCollection = class(TCollection)
private
FOwner: TPersistent;
function GetItem(Index: Integer): TMyCollectionItem;
procedure SetItem(Index: Integer; Value: TMyCollectionItem);
protected
function GetOwner: TPersistent; override;
public
constructor Create(AOwner: TPersistent);
function Add: TMyCollectionItem;
function Insert(Index: Integer): TMyCollectionItem;
property Items[Index: Integer]: TMyCollectionItem read GetItem
write SetItem;
end;
TMyPersistent = class(TPersistent)
private
FOwner: TPersistent;
FCollectionProp: TMyCollection;
procedure SetCollectionProp(Value: TMyCollection);
protected
function GetOwner: TPersistent; override;
public
procedure Assign(Source: TPersistent); override;
constructor Create(AOwner: TPersistent);
destructor Destroy; override;
published
property CollectionProp: TMyCollection read FCollectionProp
write SetCollectionProp;
end;
TMyComponent = class(TComponent)
private
FPersistentProp: TMyPersistent;
procedure SetPersistentProp(Value: TMyPersistent);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property PersistentProp: TMyPersistent read FPersistentProp
write SetPersistentProp;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyComponent]);
end;
{ TMyCollectionItem }
procedure TMyCollectionItem.Assign(Source: TPersistent);
begin
if Source is TMyCollectionItem then
FStringProp := TMyCollectionItem(Source).FStringProp
else
inherited Assign(Source);
end;
function TMyCollectionItem.GetDisplayName: String;
begin
Result := Format('Item %d',[Index]);
end;
{ TMyCollection }
function TMyCollection.Add: TMyCollectionItem;
begin
Result := TMyCollectionItem(inherited Add);
end;
constructor TMyCollection.Create(AOwner: TPersistent);
begin
inherited Create(TMyCollectionItem);
FOwner := AOwner;
end;
function TMyCollection.GetItem(Index: Integer): TMyCollectionItem;
begin
Result := TMyCollectionItem(inherited GetItem(Index));
end;
function TMyCollection.GetOwner: TPersistent;
begin
Result := FOwner;
end;
function TMyCollection.Insert(Index: Integer): TMyCollectionItem;
begin
Result := TMyCollectionItem(inherited Insert(Index));
end;
procedure TMyCollection.SetItem(Index: Integer; Value: TMyCollectionItem);
begin
inherited SetItem(Index, Value);
end;
{ TMyPersistent }
procedure TMyPersistent.Assign(Source: TPersistent);
begin
if Source is TMyPersistent then
CollectionProp := TMyPersistent(Source).FCollectionProp
else
inherited Assign(Source);
end;
constructor TMyPersistent.Create(AOwner: TPersistent);
begin
inherited Create;
FOwner := AOwner;
FCollectionProp := TMyCollection.Create(Self);
end;
destructor TMyPersistent.Destroy;
begin
FCollectionProp.Free;
inherited Destroy;
end;
function TMyPersistent.GetOwner: TPersistent;
begin
Result := FOwner;
end;
procedure TMyPersistent.SetCollectionProp(Value: TMyCollection);
begin
FCollectionProp.Assign(Value);
end;
{ TMyComponent }
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FPersistentProp := TMyPersistent.Create(Self);
end;
destructor TMyComponent.Destroy;
begin
FPersistentProp.Free;
inherited Destroy;
end;
procedure TMyComponent.SetPersistentProp(Value: TMyPersistent);
begin
FPersistentProp.Assign(Value);
end;
end.
But may I say that you can also inherit from TOwnedCollection, which makes the use and the declaration of TMyCollection much simpler:
TMyCollection = class(TOwnedCollection)
private
function GetItem(Index: Integer): TMyCollectionItem;
procedure SetItem(Index: Integer; Value: TMyCollectionItem);
public
function Add: TMyCollectionItem;
function Insert(Index: Integer): TMyCollectionItem;
property Items[Index: Integer]: TMyCollectionItem read GetItem
write SetItem;
end;

Base class's class procedure should instantiate a descendant's object?

Why in the code below, do I get the "Failed" message rather than "Succeeded"
Background: I like to have class procedures that instantiate their owner object, do something, and then free it.
However, this approach doesn't work if I have a descendant object:
Any suggestions on how to provide class procedures in a base class that can be called as a child? Am I thinking about this wrongly?
Type
TBase = class(TObject)
Protected
Procedure Proc1; Virtual;
Public
Class Procedure MyClassProc;
end;
Class Procedure TBase.MyClassProc;
Var
Base: TBase;
begin
Base := TBase.Create;
Base.Proc1;
Base.Free;
end;
Procedure TBase.Proc1;
begin
Assert(FALSE, 'Failed');
end;
type
TChild = class(TBase)
protected
Procedure Proc1; Override;
end;
Procedure TChild.Proc1;
begin
ShowMessage('Succeeded');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TChild.MyClassProc;
end;
You can do it easily with meta-programmation! Just change "TBase.Create" to "Self.Create" "self" represents the current class, it doesn't metter if is a base o a child class.
Type
TBase = class(TObject)
Protected
Procedure Proc1; Virtual;
Public
Class Procedure MyClassProc;
end;
Class Procedure TBase.MyClassProc;
Var
MyObject: TBase;
begin
// MyObject := TBase.Create;
MyObject := Self.Create; // The Magic goes here, self is the class that's calling this method, in this case, TChild }
MyObject.Proc1;
MyObject.Free;
end;
Procedure TBase.Proc1;
begin
Assert(FALSE, 'Failed');
end;
type
TChild = class(TBase)
protected
Procedure Proc1; Override;
end;
Procedure TChild.Proc1;
begin
ShowMessage('Succeeded');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TChild.MyClassProc;
end;
Strip everything down to the bare minimum, and you will see that you only ever create a TBase instance, so consequently only TBase.Proc1() will ever be called. If you want to have TChild.Proc1() be called you need to create a TChild instance and let polymorphism work its magic.
There could however be better ways to achieve your goal (whatever it is) than to have a class method create an object instance to do something. Maybe you should clarify your question.
Here it is
Add
TBase = class;
TBaseClass = class of TBase;
TBase = class(TObject)
protected
class function GetBaseClass: TBaseClass; virtual;
function TBase.GetBaseClass: TBaseClass;
begin
Result := TBase;
end;
TChild = class(TBase)
protected
class function GetBaseClass: TBaseClass; override;
function TChild.GetBaseClass: TBaseClass;
begin
Result := TChild;
end;
Change
from
Base := TBase.Create;
to
Base := GetBaseClass.Create;
Enjoy your work
Cheer
A Pham

Resources