Delphi component property class depending on the component's Owner class - delphi

I'm using RAD Studio XE5 to build my application.
I saw that is was not very practical to try tu publish properties on a TForm. It then has to be registered and installed as a package, then it's not practical for heavy development.
So, I decided I would create a non visual component (TFormPropertiesEditor) that would be used to fill up form properties. A way of standardising my forms.
The component would be dropped on the base form, a form of which every other form inherits (let's call it TBaseForm). So, the component would be dropped only once on the 'base' form, then with inheritance, every other form would have it too.
The created component would detect the class of its Owner (BaseForm or its descendents) and create an object accessible through the 'Properties' property, whose class would be conditional to the owner class.
This way, when inspecting the component on a TBaseForm, I would have access to the TBaseFormProperties only. When inspecting the component on a TSecondForm, I would also have access to the TSecondFormProperties. Only, the component would be intelligent enough to detect which PropertyClass it should expose as the Properties property.
The component would inspect the form, through the GetPropertiesClass, defined as :
function TBaseForm.GetPropertiesClass : TPropertiesClass;
begin
Result := TBaseFormProperties;
end;
function TSecondForm.GetPropertiesClass : TPropertiesClass;
begin
Result := TSecondFormProperties;
end;
Each form has a corresponding TProperties descendent, like so :
TBaseForm ------------ TSecondForm ------------- ...
|
TBaseFormProperties -- TSecondFormProperties --- ...
For example :
If the Form on which the component is placed is TBaseForm, FProperties would be a TBaseFormProperties. If the form is a TSecondForm, FProperties would be TSecondFormProperties. Naturally, TSecondFormProperties would inherit from TBaseFormProperties.
Though, when I place the component on the form, it seems to be unable to detect of which class the component is.
function TFormPropertiesEditor.GetPropertiesClass: TFormPropertiesClass;
begin
Result := TBaseForm(Owner).GetPropertiesClass;
end;
It looks like the TBaseForm(Owner) part is causing the problem. The interpreter is stuck on the TBaseForm, and will not consider if Owner is of type TSecondForm or TThirdForm.
Interfaces
So, to get around the TBaseForm(Owner) typecasting, I decided to use an interface. So if I use an interface that declares the GetPropertiesClass:
IMasterForm = interface(IInterface)
['{B6122F34-65C4-4701-8A5E-50C8DABF5516}']
function GetPropertiesClass : TFormPropertiesClass;
end;
type
TBaseForm = class(TForm, IMasterForm)
MyFormPropertiesEditor1: TMyFormPropertiesEditor;
private
{ Déclarations privées }
public
function GetPropertiesClass : UCommon.TFormPropertiesClass;
end;
The following :
function TFormPropertiesEditor.GetPropertiesClass : TFormPropertiesClass;
begin
Result := (Owner as IMasterForm).GetPropertiesClass;
end;
Results into an Interface not supported error.
Abstract Ancestor Method
Then, I decided to add an extra layer of ancestry. I've added a class, TMasterForm, from which TBaseForm inherits. This TMasterForm declares GetPropertiesClass as abstract and virtual :
TMasterForm = class(TForm, IMasterForm)
public
function GetPropertiesClass : TFormPropertiesClass; virtual; abstract;
end;
type
TBaseForm = class(TMasterForm)
private
{ Déclarations privées }
public
function GetPropertiesClass : UCommon.TFormPropertiesClass; override;
end;
But then, I get an AV because I think the IDE tries to access TMasterClass.GetPropertiesClass, which is of course not implemented.
How can this TypeCasting be accomplished? Any idea how could I proceed ?
Thank you very much in advance
Download Sample Project https://www.wetransfer.com/downloads/b524438609fc04257af803a8e3dd2eca20141225161239/764d108d335b9d296c3004dfea04a54620141225161240/9c8cc0

The basic problem here is that the IDE does not instantiate your form at designtime. So, no matter what code you put in the form class, it won't be executed by the IDE. This is because you did not register the form with the IDE.
If you wish the IDE to have knowledge of your forms then you need to register them with the IDE. And at that point all your code becomes needless because you are back to doing what you are attempting to avoid. Namely registering the forms with the IDE. You are stuck in a Catch 22 situation. If you need the IDE to know about the forms then you need to register them. At which point you may as well just surface the properties directly in the Object Inspector.

The problem in your code is that you are not inheriting your GetPropertiesClass method properly.
Infact you are not inheriting it across the class family.
In your code each class type has its own version of GetPropertiesClass method and therefore since you are typecasting the Owner to a TBaseForm class the method from TBaseForm is being used even if Owner is of TSecondForm class.
So you need to make sure that GetPropertiesClass in TBaseForm class is virtual and that merhod GetPropertiesClass in TSecondForm is overrided.
This will ensure that TSecondForm.GetProperties method will be called even when you are typecasting Owner to TBaseClass when the owner is of TSeconfForm class.

Related

How to setup an Indy custom TIdIOHandler?

I want to use a custom TIdIOHandler for my Indy TCP client and I don't know how to set it up. I create a new IOHandler class, I registered with TIdIOHandler.RegisterIOHandler, and then I use TIdTCPClient.CreateIOHandler with my new handler reference. Now, when I try to write to the TIdTCPClient.Socket I get "Abstract error" exception... Do I need to configure other things besides my example ?
TIdEnhancedIOHandler = class(TIdIOHandlerSocket)
public
function Acknowledge(Command: Cardinal = 0): Boolean;
end
{this is a client in a thread}
constructor TMyTCPClient.Create(const AHost: String; APort: Word);
begin
inherited Create;
FreeOnTerminate:= True;
TIdEnhancedIOHandler.RegisterIOHandler;
TCPClient:= TIdTCPClient.Create;
TCPClient.CreateIOHandler(TIdEnhancedIOHandler);
TCPClient.ConnectTimeout:= 1000;
TCPClient.ReadTimeout:= -1;
TCPClient.Host:= AHost;
TCPClient.Port:= APort;
RetSuccess:= False;
RetMessage:= 'Unknown error.';
end;
procedure TMainClient.Execute;
var CMD: Cardinal;
begin
TCPClient.Connect;
TCPClient.Socket.Write(CMD);
end;
From the Indy Help I understand that ...
The help is old, and has outdated information.
I must use CreateIOHandler and specify my custom IOHandler class.
You can do that, but you don't actually need to. You can simply assign an instance of your custom class directly to the TIdTCPClient.IOHandler property, before calling TIdTCPClient.Connect(), eg:
TCPClient := TIdTCPClient.Create;
TCPClient.IOHandler :- TIdEnhancedIOHandler.Create(TCPClient);
...
I do that and I get an exception which says that this class is not installed.
That means Indy's internal GIOHandlerClassList does not contain any class types that derive from the class type you specify to CreateIOHandler().
How cand I install it ? The variable GIOHandlerClassList that holds the list is private...
You would need to call the public RegisterIOHandler() or SetDefaultClass() class method on your custom class type at runtime, such as in your unit's initialization section, eg:
initialization
TIdEnhancedIOHandler.RegisterIOHandler();
However, the only thing in Indy that actually uses CreateIOHandler() is TIdSimpleServer, so there is no need to "register" your custom class in this situation.
On the other hand, TIdTCPClient.Connect() does call TIdIOHandler.MakeDefaultIOHandler() if no IOHandler is assigned, and that uses GIOHandlerClassDefault, which is set by TIdIOHandler.SetDefaultClass() (which also calls RegisterIOHandler()). So, if you want TIdTCPClient to create an object of your custom class type for you, you would need to call SetDefaultClass() at least, eg:
initialization
TIdEnhancedIOHandler.SetDefaultClass();
Either way, RegisterIOHandler()/SetDefaultClass() should be called only one time, such as at program startup.
UPDATE: you are getting an abstract error because you are deriving your class from TIdIOHandlerSocket, which is an abstract class, as it does not implement the ReadDataFromSource() and WriteDataToTarget() methods that are declared as abstract in TIdIOHandler. You need to derive your class from TIdIOHandlerStack instead, which derives from TIdIOHandlerSocket and implements those methods.

Is it always safe to remove a published empty section?

I'm working on an old legacy project which have several classes in which the published section is always declared without anything inside of it, i.e:
TMyClass = class
public
procedure DoSomething();
published
end;
On compiling, I get the following warning message:
[DCC Warning] uMyUnit.pas(141): W1055 PUBLISHED caused RTTI ($M+) to
be added to type 'TMyClass'
I don't know if the predecessor developer has declared these published sections for some valid reason.
Is it always safe to remove an empty published section or could it cause some changes in the application's behavior?
The difference for the class itself is none - however the important thing is that the default visibility of any class that inherits from a class with {$M+} then changes from public to published!
See this example code:
uses
TypInfo;
type
TMyClass = class
private
fName: string;
property Name: string read fName;
published
end;
TMyOtherClass = class(TMyClass)
property Name;
end;
var
propCount, i: Integer;
props: PPropList;
begin
propCount := GetPropList(TypeInfo(TMyOtherClass), props);
for i := 0 to propcount - 1 do
Writeln(props^[i].Name);
Readln;
end.
You can see that it lists the Name property but when you remove the published from TMyClass it will not - that is because once TMyClass got {$M+} added any member declared without explicitly stating the visibility it will be published opposed to public.
Also other members declared without visibility like fields will be published. This is being used in the streaming system Delphi uses for forms and such.
You can for example then call TObject.FieldAddress or TObject.MethodAddress passing in the name of a field or method and get back pointers to the field or method. It only works with published fields and methods.
This is how loading from a dfm sets up all those IDE generated fields like Button1 or connects the Button1Click method to the Button1.OnClick - they are without explicit visibility at the top of your form which inherits from TComponent that has {$M+} declared.
It depends
Does the rest of the code actually require access to RTTI for the class?
Only TPersistent-derived classes have the {$M+} directlive applied to them by default without needing a published section.
A published section is used for DFM streaming, which requires RTTI. Non-persistent classes are not steamed in DFMs, but there are other uses for RTTI.
So, without knowing what the rest of the code does, it is not really known whether removing empty published sections is safe or not.

Does interface delegation of an inherited interface require a wrapper class?

Delphi allows for interface delegation using the implements keyword.
For example
IIndep1 = interface
function foo2: integer;
end;
IIndep2 = interface
function goo2: integer;
end;
TIndep1And2 = class(TInterfacedObject, IIndep1, IIndep2)
private
FNested : IIndep1; //e.g. passed via constructor or internally created (not shown here)
public
Constructor Create(AIndep1: IIndep1);
function goo2: integer;
property AsIndep1 : IIndep1 read FNested implements IIndep1;
end;
That works well, but not for inherited interfaces. (Error message "Missing implementation of interface method ILev1.foo")
ILev1 = interface
function foo: Integer;
end;
ILev2 = interface(ILev1)
function goo: Integer;
end;
TLev2Fails = class(TInterfacedObject, ILev1, ILev2) //Also fails with ILev2 alone (Error: "ILev1 not mentioned in interface list")
private
FNested : ILev1; //passed via constructor or internally created
public
Constructor Create(AILev1: ILev1);
function goo: Integer;
property AsLev1 : ILev1 read FNested implements ILev1;
end;
The workaround is to add an extra ancestor class
TLev1Wrapper = class(TInterfacedObject, ILev1)
private
FNested : ILev1; //passed via constructor or internally created
public
Constructor Create(AILev1: ILev1);
property AsLev1 : ILev1 read FNested implements ILev1;
end;
TLev2Works = class(TLev1Wrapper, ILev2)
public
function goo: Integer;
end;
Is there a way to avoid the wrapper class ancestor?
[EDIT]
Just a note on interface delegation, the purpose of using implements is to avoid satisfying the interface directly, but passing that requirement to an aggregated or composed member. Providing the full interface and manually delegating to a composed member defeats the benefits gained from using implements to direct the interface. In fact, in that case the implements keyword and property may be removed.
This looks like the compiler is attempting to enforce the expectations (read: requirements) of IUnknown.QueryInterface:
For any one object, a specific query for the IUnknown interface on any
of the object's interfaces must always return the same pointer value.
If you were able to delegate the implementation of a base interface whilst implementing a derived interface yourself then:
obj := TLev2Fails.Create(otherLev1); // Assuming your class could compile
lev1 := obj as ILev1; // yields reference to otherLev1 implementor
lev2 := obj as ILev2; // yields reference to TLev2Fails instance
unk1 := lev1 as IUnknown; // returns IUnknown of otherLev1 implementor
unk2 := lev2 as IUnknown; // returns IUnknown of obj TLev2fails instance
This would not be the case if your nested object were correctly implemented as a TAggregatedObject derived class, but the compiler has no way of knowing whether this is the case let alone enforcing it so instead it appears it simply requires that if you implement a derived interface then you must also directly implement any interfaces that interface itself inherits.
The compiler error in this situation isn't being very helpful, though it could be read as telling you what you need to do, just not why you need to do it in this case, which is not that unusual for compiler errors.
If you wish to delegate in this case then you must "delegate manually".
Whilst this loses the benefit of the implements facility, it does at least retain the benefits of re-use, just not quite as conveniently.
NOTE: Even if your delegated implementation is based on a TAggregatedObject, the compiler still cannot determine that these implementation details satisfies the requirements of QueryInterface so you will still get this error (even if using a class reference for the delegated interface).
You must still delegate manually.
Having said all that, I cannot currently see how this is any different for the case when interfaces are involved with no inheritance relationship, but it is quite possible that this is valid and I just haven't worked through all the necessary 'thought experiments' to prove it to myself. :)
It may be that the compiler is just being extra cautious in situations when it thinks it can/should be and the documentation simply fails to mention this resulting limitation of implements.
Or it may be a bug in the compiler, though I think there are sufficiently apparent reasons for the behaviour and the behaviour is itself so well established and consistent (having reproduced the exact same behaviour in Delphi 7) that an omission in relevant documentation is the more likely explanation.
Is there a way to avoid the wrapper class ancestor?
No, the inherited interface has to be bound to a an ancestor class. Delphi does not implicitly bind inherited interfaces, because of reasons explained here. Also, it cannot bind to the current class, unless it manually delegates all calls to the composed item.
We are left with only one option. An ancestor wrapper class, to which we can bind ILev1

Delphi Web Script: How to Expose a Class via RTTI which contains a Method returning another (exposed) Class

I have this Delphi class
type
TAnotherClass = class
end;
TMyClass = class
function Foo: TAnotherClass;
end;
function TMyClass.Foo: TAnotherClass;
begin
Result := TAnotherClass.Create;
end;
Now I'd like to expose this class via "dwsRTTIExposer.pas":
myUnit.ExposeRTTI(TypeInfo(TMyClass));
myUnit.ExposeRTTI(TypeInfo(TAnotherClass));
My Script looks like that:
var a: TMyClass = TMyClass.Create;
var b: TAnotherClass;
b := a.Foo;
Unfortunatelly Delphi Web Script doesn't recognize the return value from TMyClass.Foo as a valid Script Class. Is there a possibility to do that without falling back to manually expose each method with an OnEval-Eventhandler?
ExposeRTTI currently doesn't support parameters of class type.
This is because returning a direct Delphi class in the script can be problematic, as the life-cycle of Delphi objects is arbitrary and undetermined (the Delphi-side object can be destroyed at any time without notice f.i.).
You don't have to expose each method manually, you can use the RTTI exposer for every methods that involves basic types, and only have to deal manually with methods involving class types.
That'll then leave you with having to decide how you want the script-side objects to be exposed, and what their relationship to the Delphi-side object is, which is something the RTTI provides no clues about.
For instance with your original code, the OnEval code would just create a new script object that wraps the method Result for each call.
But the RTTI signature of Foo would still be exactly the same if its implementation was changed to something like
TMyClass = class
private
FFoo: TAnotherClass;
public
function Foo: TAnotherClass;
end;
function TMyClass.Foo: TAnotherClass;
begin
if FFoo=nil then
FFoo := TAnotherClass.Create;
Result := FFoo;
end;
However in that case, the OnEval would have to be completely different, as you would have to return the same script-side object on subsequent calls, and you would also need to hook the script-side object's destructor to properly handle the consequences on the private FFoo field.
Once Delphi has truly garbage-collected objects, the constraint could be relaxed, but currently the only thing that gets close is TInterfacedObject, which is unsafe, and you still have to deal with manual event handlers to handles things like circular references or classes that disable the reference counting (like the VCL components).

How to inherit if the child class is TForm?

I admit this is the first time I use inheritance,so I might even have choosen the wrong way,that's why I'm here asking you.
I wrote a Message Handler in my delphi application to catch the messages from WSAAsyncSelect()
procedure FormMain.MessageHandler(var Msg:Tmessage);
begin
case WSAGetSelectEvent(MSG.LParam) of
FD_READ: //OnSocketRead(MSG.WParam);
FD_CLOSE: //OnSocketClose(MSG.WParam);
end;
end;
The problem is that OnSockerRead and OnSocketClose are functions in another class.
I want to make a good relationship between the classes so the class with those two functions can access it's parent ,but in the same time the things to be private to other classes.
Please show me an example how should I do it,because I don't know if it's better to be abstract or inherited since I have never used both of them.I want to make my code more OO.
Thank you!
One thing you can do is to use interfaces to gain access to main form functionality. For example, lets say that you want to call either SocketRead or SocketClose which are on the main form from your child form. you COULD just use mainform in the implementation of the unit, but I try to avoid these types of circular references. The other option is to create a new unit to contain a shared interface and use it by both the main form and the child unit. For example:
unit MainFormShared;
interface
type
IMainFormShared = interface
['{A2C624D5-DDCF-49D6-8B03-791BA0B79A42}']
procedure SocketRead(var Handle : Integer);
procedure SocketClose(Var Handle : Integer);
end;
implementation
end.
your main form would implement this interface (ok to keep the implementation private):
type
tMainForm = class(TForm,IMainFormShared)
:
private
procedure SocketRead(var Handle : Integer);
procedure SocketClose(Var Handle : Integer);
end;
From the parent object in your inheritance chain you can implement your message handler like so:
procedure TParentForm.MessageHandler(var Msg:Tmessage);
var
fMainFormShared : IMainFormShared;
begin
case WSAGetSelectEvent(MSG.LParam) of
FD_READ:
if Supports(Application.MainForm, IMainFormShared,fMainFormShared) then
fMainFormShared.SocketRead(Msg.WParam);
FD_CLOSE: //OnSocketClose(MSG.WParam);
if Supports(Application.MainForm, IMainFormShared,fMainFormShared) then
fMainFormShared.SocketClose(Msg.WParam);
end;
end;
I don't think inheritance is the answer here, unless that OtherClass can be derived from MainForm, but that looks doubtful.
One way to open up access is to put both classes in the same Unit. That gives them instant access to each others implementation details.
But maybe you are trying to hard here, if OtherClass in it's own (small) unit that nobody else is USES then it won't be that bad to make those functions public.

Resources