Trying to combining generics and operator overloading in Delphi [duplicate] - delphi

I'm new in Delphi. For a project required by my company, I need to translate some code from our existing C++ classes to Delphi. Some of these classes are templates, such as:
template <class T>
struct APoint
{
T m_X;
T m_Y;
virtual void Add(T value);
};
template <class T>
void APoint<T>::Add(T value)
{
m_X += value;
m_Y += value;
}
I use it e.g. with this code
APoint<float> pt;
pt.m_X = 2.0f;
pt.m_Y = 4.0f;
pt.Add(5.0f);
and this works well.
Now I need to write equivalent code for Delphi. I tried to write a Delphi Generic class, based on the C++ code above:
APoint<T> = record
m_X: T;
m_Y: T;
procedure Add(value: T);
end;
procedure APoint<T>.Add(value: T);
begin
m_X := m_X + value;
m_Y := m_Y + value;
end;
However this code does not compile. I get this error:
E2015 Operator not applicable to this operand type
AFAIK this code should work, and I don't understand what is wrong with it. So can anybody explain to me:
Why a such code does not compile in Delphi?
What is the correct (and simplest) way in Delphi to create a template class that provides an Add() function, as closest as possible to the C++ code and usage above?
EDITED on 17.10.2016
Thanks for all the replies. So if I understood correctly, there is no way to create a c++-like style template, because Delphi imposes several constraints that not exists in c++.
Based on that, I searched a workaround to reach the objective I want. I found the following solution:
IPoint<T> = interface
procedure Add(value: T);
end;
APoint<T> = class(TInterfacedObject, IPoint<T>)
m_X: T;
m_Y: T;
procedure Add(value: T); virtual; abstract;
end;
APointF = class(APoint<Single>)
destructor Destroy; override;
procedure Add(value: Single); reintroduce;
end;
destructor APointF.Destroy;
begin
inherited Destroy;
end;
procedure APointF.Add(value: Single);
begin
m_X := m_X + value;
m_Y := m_Y + value;
end;
I use it e.g. with this code
procedure AddPoint;
var
pt: IPoint<Single>;
begin
pt := APointF.Create;
APointF(pt).m_X := 2.0;
APointF(pt).m_Y := 4.0;
APointF(pt).Add(5.0);
end;
and this works well. However I find the style a little heavy, e.g. the necessity to use APointF(pt). So, in relation to code above, my questions are:
Is this solution a good solution? (i.e. better to write a version of each record for each type I want to support, like e.g APointF, APointI, APointD, ...)
Is there a way to simplify this code, e.g. a solution to call pt.m_X directly without the APointF(pt) conversion? (NOTE I omitted here the implementation of properties, even if I think them more elegant than accessing the variable directly)
What about the performances of this solution? (I.e. is this solution drastically slower than a direct m_X := m_X + value addition?)
Finally, I saw another solution in the Delphi code, where it is possible to implement an equality comparison of 2 generic types this way:
function APoint<T>.IsEqual(const other: APoint<T>): Boolean;
var
comparer: IEqualityComparer<T>;
begin
Result := (comparer.Equals(m_X, other.m_X) and comparer.Equals(m_Y, other.m_Y));
end;
I tried to read the code behind the scene, however I found it terribly complicated. So, my questions are:
Is a such solution better than the one above proposed?
Is there a similar ready-to-use solution for mathematical operations?
Are the performance of a such solution acceptable?
Thanks in advance for your replies
Regards

Delphi Generics are intrinsically different from C++ template, and resemble more their C# counterpart.
In C++ you can do any operation on template types, and at time of the template instantiation the compiler checks that the operation you are performing in the template are available for the specific type you are using. If not you get a compiler error.
In Delphi (and many other languages) you declare a generic type possibly providing some declarative constraints, and those constraints -- base classes or interface -- determine the operations you can do on the generic type. At instantiation time, the only check is if the declared type fits the constraint.
Arguably, the Delphi language could add constraints for floating point or ordinal types, but this would provide a very limited flexibility (changing the floating or integer type you can use in the generic instance). I personally don't regard this as a critical feature.

Delphi generics do not support arithmetic operators that act on generic types. In order for the compiler to accept the code it needs to know that each operation on a generic type is going to be available upon instantiation.
Generic constraints allow you to tell the compiler what capabilities the type has. However generic constraints do not allow you to tell the compiler that the type supports arithmetjc operators.
Unfortunately what you are trying to do is simply not possible. For sure you can construct frameworks yourself that can use tools like interfaces to get the arithmetic performed but doing so gives up performance. If that is acceptable then fine. Otherwise you are best biting the bullet and avoiding generics here.
Oh for C++ templates.

Pointing this out for anyone else looking for an Object Pascal solution to this general issue:
Note that Free Pascal quite simply does support exactly what they're trying to do here (even in "Delphi-syntax compatibility" mode.)
As someone who had only used Free Pascal for a long time, I was honestly very surprised that Delphi doesn't allow this at all, once I realized that was the case. It's a significant limitation IMO.
Valid Free Pascal code:
program Example;
// Using Delphi-mode here allows us to declare and use
// generics with the same syntax as Delphi, as opposed to
// needing the "generic" and "specialize" keywords that
// FPC normally requires.
{$mode Delphi}
// To allow +=, another nice FPC feature...
{$COperators On}
type
TGPoint<T> = record
X, Y: T;
// static class function instead of constructor here so we can inline it
class function Create(constref AX, AY: T): TGPoint<T>; static; inline;
// define our operator overload
class operator Add(constref Left, Right: TGPoint<T>): TGPoint<T>; inline;
end;
class function TGPoint<T>.Create(constref AX, AY: T): TGPoint<T>;
begin
with Result do begin
X := AX;
Y := AY;
end;
end;
class operator TGPoint<T>.Add(constref Left, Right: TGPoint<T>): TGPoint<T>;
begin
with Result do begin
X := Left.X + Right.X;
Y := Left.Y + Right.Y;
end;
end;
var SP: TGPoint<String>;
begin
SP := TGPoint<String>.Create('Hello, ', 'Hello, ');
SP += TGPoint<String>.Create('world!', 'world!');
with SP do begin
WriteLn(X);
WriteLn(Y);
end;
end.
The program of course prints:
Hello, world!
Hello, world!

Related

Arithmetic operations with generic types in Delphi

I'm new in Delphi. For a project required by my company, I need to translate some code from our existing C++ classes to Delphi. Some of these classes are templates, such as:
template <class T>
struct APoint
{
T m_X;
T m_Y;
virtual void Add(T value);
};
template <class T>
void APoint<T>::Add(T value)
{
m_X += value;
m_Y += value;
}
I use it e.g. with this code
APoint<float> pt;
pt.m_X = 2.0f;
pt.m_Y = 4.0f;
pt.Add(5.0f);
and this works well.
Now I need to write equivalent code for Delphi. I tried to write a Delphi Generic class, based on the C++ code above:
APoint<T> = record
m_X: T;
m_Y: T;
procedure Add(value: T);
end;
procedure APoint<T>.Add(value: T);
begin
m_X := m_X + value;
m_Y := m_Y + value;
end;
However this code does not compile. I get this error:
E2015 Operator not applicable to this operand type
AFAIK this code should work, and I don't understand what is wrong with it. So can anybody explain to me:
Why a such code does not compile in Delphi?
What is the correct (and simplest) way in Delphi to create a template class that provides an Add() function, as closest as possible to the C++ code and usage above?
EDITED on 17.10.2016
Thanks for all the replies. So if I understood correctly, there is no way to create a c++-like style template, because Delphi imposes several constraints that not exists in c++.
Based on that, I searched a workaround to reach the objective I want. I found the following solution:
IPoint<T> = interface
procedure Add(value: T);
end;
APoint<T> = class(TInterfacedObject, IPoint<T>)
m_X: T;
m_Y: T;
procedure Add(value: T); virtual; abstract;
end;
APointF = class(APoint<Single>)
destructor Destroy; override;
procedure Add(value: Single); reintroduce;
end;
destructor APointF.Destroy;
begin
inherited Destroy;
end;
procedure APointF.Add(value: Single);
begin
m_X := m_X + value;
m_Y := m_Y + value;
end;
I use it e.g. with this code
procedure AddPoint;
var
pt: IPoint<Single>;
begin
pt := APointF.Create;
APointF(pt).m_X := 2.0;
APointF(pt).m_Y := 4.0;
APointF(pt).Add(5.0);
end;
and this works well. However I find the style a little heavy, e.g. the necessity to use APointF(pt). So, in relation to code above, my questions are:
Is this solution a good solution? (i.e. better to write a version of each record for each type I want to support, like e.g APointF, APointI, APointD, ...)
Is there a way to simplify this code, e.g. a solution to call pt.m_X directly without the APointF(pt) conversion? (NOTE I omitted here the implementation of properties, even if I think them more elegant than accessing the variable directly)
What about the performances of this solution? (I.e. is this solution drastically slower than a direct m_X := m_X + value addition?)
Finally, I saw another solution in the Delphi code, where it is possible to implement an equality comparison of 2 generic types this way:
function APoint<T>.IsEqual(const other: APoint<T>): Boolean;
var
comparer: IEqualityComparer<T>;
begin
Result := (comparer.Equals(m_X, other.m_X) and comparer.Equals(m_Y, other.m_Y));
end;
I tried to read the code behind the scene, however I found it terribly complicated. So, my questions are:
Is a such solution better than the one above proposed?
Is there a similar ready-to-use solution for mathematical operations?
Are the performance of a such solution acceptable?
Thanks in advance for your replies
Regards
Delphi Generics are intrinsically different from C++ template, and resemble more their C# counterpart.
In C++ you can do any operation on template types, and at time of the template instantiation the compiler checks that the operation you are performing in the template are available for the specific type you are using. If not you get a compiler error.
In Delphi (and many other languages) you declare a generic type possibly providing some declarative constraints, and those constraints -- base classes or interface -- determine the operations you can do on the generic type. At instantiation time, the only check is if the declared type fits the constraint.
Arguably, the Delphi language could add constraints for floating point or ordinal types, but this would provide a very limited flexibility (changing the floating or integer type you can use in the generic instance). I personally don't regard this as a critical feature.
Delphi generics do not support arithmetic operators that act on generic types. In order for the compiler to accept the code it needs to know that each operation on a generic type is going to be available upon instantiation.
Generic constraints allow you to tell the compiler what capabilities the type has. However generic constraints do not allow you to tell the compiler that the type supports arithmetjc operators.
Unfortunately what you are trying to do is simply not possible. For sure you can construct frameworks yourself that can use tools like interfaces to get the arithmetic performed but doing so gives up performance. If that is acceptable then fine. Otherwise you are best biting the bullet and avoiding generics here.
Oh for C++ templates.
Pointing this out for anyone else looking for an Object Pascal solution to this general issue:
Note that Free Pascal quite simply does support exactly what they're trying to do here (even in "Delphi-syntax compatibility" mode.)
As someone who had only used Free Pascal for a long time, I was honestly very surprised that Delphi doesn't allow this at all, once I realized that was the case. It's a significant limitation IMO.
Valid Free Pascal code:
program Example;
// Using Delphi-mode here allows us to declare and use
// generics with the same syntax as Delphi, as opposed to
// needing the "generic" and "specialize" keywords that
// FPC normally requires.
{$mode Delphi}
// To allow +=, another nice FPC feature...
{$COperators On}
type
TGPoint<T> = record
X, Y: T;
// static class function instead of constructor here so we can inline it
class function Create(constref AX, AY: T): TGPoint<T>; static; inline;
// define our operator overload
class operator Add(constref Left, Right: TGPoint<T>): TGPoint<T>; inline;
end;
class function TGPoint<T>.Create(constref AX, AY: T): TGPoint<T>;
begin
with Result do begin
X := AX;
Y := AY;
end;
end;
class operator TGPoint<T>.Add(constref Left, Right: TGPoint<T>): TGPoint<T>;
begin
with Result do begin
X := Left.X + Right.X;
Y := Left.Y + Right.Y;
end;
end;
var SP: TGPoint<String>;
begin
SP := TGPoint<String>.Create('Hello, ', 'Hello, ');
SP += TGPoint<String>.Create('world!', 'world!');
with SP do begin
WriteLn(X);
WriteLn(Y);
end;
end.
The program of course prints:
Hello, world!
Hello, world!

Why Delphi XE3 gives "E2382 Cannot call constructors using instance variables"?

I have a simple piece of code, that compiles in Delphi XE2 but not in XE3, and I don't know why. I have reduced the problematic code to a small bit and would like to know what's wrong with it in Delphi's opinion. Trying to compile a project containing this unit in Delphi XE 2 works fine, but in Delphi XE3 (trial), it gives "[dcc32 Error] AffineTransform.pas(26): E2382 Cannot call constructors using instance variables". The only "eccentric" thing I know of here is the use of the old-school "object" type, where the constructor isn't really exactly the same thing as in real objects (TObject-based class instances).
If I replace the words 'constructor' in this object with 'procedure', then it compiles ok, but why is this, and is this an ok change to do in my code, i.e. is it a change that will have no effect on the functionality?
unit AffineTransform;
interface
type
{ Rectangular area. }
TCoordRect = object
public
Left, Top, Right, Bottom: Real;
constructor CreatePos(ALeft, ATop, ARight, ABottom: Real);
procedure Include(AX, AY: Real);
end;
implementation
constructor TCoordRect.CreatePos(ALeft, ATop, ARight, ABottom: Real);
begin
Left := ALeft;
Top := ATop;
Right := ARight;
Bottom := ABottom;
end;
procedure TCoordRect.Include(AX, AY: Real);
begin
CreatePos(AX, AY, AX, AY)
end;
end.
For this legacy Turbo Pascal style object, there is really no meaning to the keyword constructor. Although an object constructor does have some special treatment, there's absolutely no need for that here. What have here is nothing more than a record with some methods.
The XE3 compiler was changed so that it no longer allows you to call a constructor on Self inside an instance method. That is the case for both class and object. I've not seen any documentation of why this change was made. No doubt in time it will seep out.
Your immediate solution is to replace constructor with procedure. In the longer term, it would make sense to turn this into a record rather than an object.
I would also council you to change the name of the method to Initialize. Some library designers seem to opt for using Create and Free methods on their records. This had led to immense amount of code being written like this:
ctx := TRttiContext.Create;
try
....
finally
ctx.Free;
end;
In fact all that code is spurious and can simply be removed! A TRttiContext variable will automatically initialize itself.
That sort of design also sets a giant Heffalump Trap for that faction of Delphi coders that like to use FreeAndNil. Passing a record to FreeAndNil leads to some interesting fireworks!
I have a simple piece of code, that compiles in Delphi XE2 but not in XE3, and I don't know why.
You are trying to call a constructor inside of a method of an instance that is already instantiated and initialiized. The compiler does not allow that anymore. More specifically, this code:
procedure TCoordRect.Include(AX, AY: Real);
begin
CreatePos(AX, AY, AX, AY)
end;
Is the same as this code:
procedure TCoordRect.Include(AX, AY: Real);
begin
Self.CreatePos(AX, AY, AX, AY)
end;
And you cannot call a constructor on the Self variable anymore. Why? IIRC, it has to do with compiler's ongoing shift to supporting mobile development.

Can I use generics to do the same operation on similar types of controls?

I am using Delphi 2010 and I have a unit where over the years I have added my own procedures and functions that can be used with any project I make, such as:
function ListBoxIsSelected(ListBox: TListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
The above uses TListBox as a parameter, so whenever the above function is used I must supply a listbox that is of TListBox class.
Now suppose I have some other component libraries that could work with the same function, For example the Jedi component classes.
How could I use the above function, when the Jedi listbox is TJvListBox class and my function is looking for TListBox class? Although both components are practically the same, the class names are different. If I provided the same function specifically for the TJvListBox it would likely work because they are both "listboxes":
function ListBoxIsSelected(ListBox: TJvListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
Now, I have whole load of procedures and functions written in the same kind of way where I need to pass a component as a parameter. Having to rewrite them again just to work with a different component class is not feasible!
How can I write this with generics?
You can't write that with generics, unless your target classes all descend from the same base class of course. (But then you wouldn't need generics for it.)
If you really want something that can check if the ItemIndex property on any object <> -1, though, you can do that with a different Delphi 2010 feature: extended RTTI.
uses
SysUtils, RTTI;
function IsSelected(item: TObject): boolean;
var
context: TRttiContext;
cls: TRttiType;
prop: TRttiProperty;
ItemIndex: integer;
begin
if item = nil then
raise Exception.Create('Item = nil');
context := TRttiContext.Create;
cls := context.GetType(item.ClassType);
prop := cls.GetProperty('ItemIndex');
if prop = nil then
raise Exception.Create('Item does not contain an ItemIndex property.');
ItemIndex := prop.GetValue(item).AsInteger;
result := ItemIndex <> -1;
end;
Careful, though. There's no compile-time type checking here, and this process is significantly slower than your original routine. You probably won't notice it, but if you call something like this in a tight loop, it will slow it down.
I don't understand how I can write this with Generics?
You can’t – not unless your component implements a common interface or inherits from a common base class with the standard ListBox, and that interface / base class offers the ItemIndex property.
In fact, this use-case isn’t such a great example of generics because using an interface or base class in the declaration would work just as well.
In this case, you can write two overloaded functions, one expecting TJvListBox and the other expecting TListBox.
In more complex cases this approach may not apply so well, but I think your case is simple enough for this solution.
I cannot look it up right now (on holiday, no Delphi), but don't TJvListBox and TListBox descend from a common ancestor (my guess would be: TCustomListBox)? In that case something like this should work:
interface
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
implementation
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := _ListBox.ItemIndex <> -1;
end;
Just in case ItemIndex (as I said: I cannot check right now) is protected in TCustomListBox, you can just use a typecast hack:
type
TListBoxHack = class(TCustomListBox)
end;
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := TListBoxHack(_ListBox).ItemIndex <> -1;
end;
(I just thought I should mention this since the original question has already been answered: Using Generics does not help here.)

RTTI: Can I Get a Type by Name?

Given a text string containing a type name, is there some way to get the appropriate type itself?
I'm looking to do something like this:
type
TSomeType<T> = class
// yadda yadda
end;
procedure DoSomething;
var
obj : TObject;
begin
o := TSomeType<GetTypeByName('integer')>.Create;
// do stuff with obj
end;
I've looked at several RTTI explanations online and looked through the Delphi units and don't see what I'm looking for. Is this possible?
No, generics are entirely compiletime.
The new RTTI unit in Delphi 2010 has a way of retrieving types declared in the interface section of units. For any given type, represented by a TRttiType instance, the TRttiType.QualifiedName property returns a name that can be used with TRttiContext.FindType later to retrieve the type. The qualified name is the full unit name (including namespaces, if they exist), followed by a '.', followed by the full type name (including outer types if it nested).
So, you could retrieve a representation of the Integer type (in the form of a TRttiType) with context.FindType('System.Integer').
But this mechanism can't be used to retrieve instantiations of generic types that weren't instantiated at compile time; instantiation at runtime requires runtime code generation.
You can always register your types into some sort of registry (managed by a string list or dictionary) and create a factory function to then return the appropriate object. Unfortunately you would have to know in advance what types you were going to need. Something similar to the Delphi functions RegisterClass and FindClass (in the classes unit). My thinking is to put the generic template type into the list directly.
An example of possible usage:
RegisterCustomType('Integer',TSomeType<Integer>);
RegisterCustomType('String',TSomeType<String>);
if FindCustomType('Integer') <> nil then
O := FindCustomType('Integer').Create;
EDIT: Here is a specific simple implementation using a tDictionary from Generics.Collections to handle the registry storage...I'll leave extracting this into useful methods as a simple exercise for the reader.
var
o : TObject;
begin
TypeDict := TDictionary<String,TClass>.Create;
TypeDict.Add('integer',TList<integer>);
if TypeDict.ContainsKey('integer') then
o := TypeDict.Items['integer'].Create;
if Assigned(o) then
ShowMessage(o.ClassName);
end;
Another EDIT: I was giving this some thought last night, and discovered another technique that you can merge into this concept. Interfaces. Here is a quick do nothing example, but can easily be extended:
TYPE
ITest = interface
['{0DD03794-6713-47A0-BBE5-58F4719F494E}']
end;
TIntfList<t> = class(TList<T>,ITest)
public
function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
procedure TForm1.Button7Click(Sender: TObject);
var
o : TObject;
fTestIntf : ITest;
begin
TypeDict := TDictionary<String,TClass>.Create;
TypeDict.Add('integer',TIntfList<integer>);
if TypeDict.ContainsKey('integer') then
o := TypeDict.Items['integer'].Create;
if Assigned(o) and Supports(o,ITest,fTestIntf) then
ShowMessage(o.ClassName);
end;
of course you would have to implement the QueryInterface, _AddRef and _Release methods and extend the interface to do something more useful.
If you forget generics and basic types, the "RegisterClass" function would be helpful. But it doesn't work for generics or basic types.

Add a property on TWinControl Class

I want to add a published property into TWinControl.
Is there someway to do this without the necessity of recompiling the base source code ?
If not, some way to recompile the base source code without too much troubles ?
Tks in advice...
EDIT 'CAUSE OF NEW IDEAS
Alright, What I'm thinking to do I'm trying to override the _GetMem from System.pas for classes
inherited from TWinControl.
Why ? 'Cause I'll alloc some extra space to the objects enough to an integer.
Why an integer ? 'Cause this way I can add any pointer to object.
So on the helper class to TWinControl I can make a Get an Set function to access this space of memory.
Good isn't it ? How to do this ?
Overrideing the GetMem procedure I can use the same strategy used on FastCode, create a jumper to the new procedure.
What I need now is understand how this memory alloc works InstanceSize to override this.
At all I'm studding how do Delphi do this... And to add this on DFM I will do the same way, I'll create a jumper to the filer.
Someone have some idea to add the new space in objects ? What method I need to override ? The jumper I know how to do.
Tks Again.
EDIT = Evolution
I think that I did the injection of memory.
I need to do more tests.
I've just did it, I'm not caring about optimizations at the moment, if some one would like to test it, here goes the code.
Just add the unit as the first unit of your project.
unit uMemInjection;
interface
uses
Controls;
type
THelperWinControl = class Helper for TWinControl
private
function RfInstanceSize: Longint;
function GetInteger: Integer;
procedure SetInteger(const Value: Integer);
public
property RfInteger: Integer read GetInteger write SetInteger;
end;
implementation
uses
Windows;
procedure SInstanceSize;
asm
call TWinControl.InstanceSize
end;
function THelperWinControl.GetInteger: Integer;
begin
Result := Integer(PInteger(Integer(Self) + (Self.InstanceSize - SizeOf(Integer)))^);
end;
function THelperWinControl.RfInstanceSize: Longint;
begin
Result := PInteger(Integer(Self) + vmtInstanceSize)^;
Result := Result + SizeOf(Integer);
end;
/////////////////////////////////////////////// FastCode ///////////////////////////////////////////////
type
PJump = ^TJump;
TJump = packed record
OpCode: Byte;
Distance: Pointer;
end;
function FastcodeGetAddress(AStub: Pointer): Pointer;
begin
if PBYTE(AStub)^ = $E8 then
begin
Inc(Integer(AStub));
Result := Pointer(Integer(AStub) + SizeOf(Pointer) + PInteger(AStub)^);
end
else
Result := nil;
end;
procedure FastcodeAddressPatch(const ASource, ADestination: Pointer);
const
Size = SizeOf(TJump);
var
NewJump: PJump;
OldProtect: Cardinal;
begin
if VirtualProtect(ASource, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
NewJump := PJump(ASource);
NewJump.OpCode := $E9;
NewJump.Distance := Pointer(Integer(ADestination) - Integer(ASource) - 5);
FlushInstructionCache(GetCurrentProcess, ASource, SizeOf(TJump));
VirtualProtect(ASource, Size, OldProtect, #OldProtect);
end;
end;
/////////////////////////////////////////////// FastCode ///////////////////////////////////////////////
{ THelperWinControl }
procedure THelperWinControl.SetInteger(const Value: Integer);
begin
PInteger(Integer(Self) + (Self.InstanceSize - SizeOf(Integer)))^ := Value;
end;
initialization
FastcodeAddressPatch(FastcodeGetAddress(#SInstanceSize), #TWinControl.RfInstanceSize);
end.
Thanks to Smasher, I remembered how the Delphi team used class helpers and a designer trick to add properties to Delphi 2007 without breaking binary compatibility with Delphi 2006.
See this great article by Hallvard Vassbotn on how to do this.
I think it solves most, if not all, of your problems.
look for these things in the article:
TCustomFormHelper = class helper for TCustomForm
The FPixelsPerInch storage hack
Injecting design-time properties
Defining the streaming properties
You'll have to work your own way to do the streaming, though, as you hook from the outside world into TWinControl, but that might be possible too.
--jeroen
Delphi2007 and higher have "class helpers".
You can introduce new functions and properties, but no fields/variables. So you have to store the value of you new property in a extra object (via factory or whatever) or (very ugly) in the .Tag property...
Don't know if class helper also work in packages/design time?
If you are using this property only on the application level, you may use the following approaches:
composition: bundle a reference to TWinControl object with other properties into new class, and pass/operate objects this class in your calls
dictionary-like functions: GetMyPropertyFor( AWinControl: TWinControl): and SetMyPropertyFor( AWinControl: TWinControl: AValue: ), which internally maintain additional property for each called TWinControl object
ADDITION: Based on your additional comment, existing Tag property should play well for your needs. You can even define 'levels' by using different values there.
No, there is no way to modify TWinControl without recompiling the VCL. Also I don't recommend changing the VCL (since having a "custom" VCL can impact the portability of your project - at the very least between Delphi installations). I would aim at making another class that inherit from TWinControl and then add your published property to this new class.
If you still want to change the VCL see the following post:
http://www.delphigroups.info/2/6/744173.html
Note that "you will no longer be able to compile using runtime
packages"...
(I know the answer is a bit dense, comment on it what details you need more info about)
What you could do is what for instance TGridPanel does: it adds the Column, Row, ColumnSpan and RowSpan 'properties' to the object inspector for all components that are on the GridPanel.
That will solve your design-time support.
I thought I had a reference on how the TGridPanel does this (and TFlowPanel does similar things), but I can't find it right now. Probably Ray Konopka explained this during a conference, but that info might not be on-line.
For run-time support, you could go with class helpers.
When using class helpers, note that only the nearest visible one for a class will apply.
Another route you might follow is to use the Tag property (which is an Integer, but you can cast it to a Pointer or a TObject), but you might be bitten by others using that too.
You'd have to create your own design-time support for those tag properties though.
--jeroen

Resources