How to register a component and property editor at run-time? - delphi

After much searching, it looks like I have to assign RegisterComponentsProc and RegisterPropertyEditorProc, which I have done.
However, I thought that I could call my design time Register function, i.e. <myComponentUnit>.Register();.
When I do I get stack overflow, because, well ...
procedure myComponentUnit.Regiter;
begin
RegisterPropertyEditor(TypeInfo(Integer),
TMyComponent, 'myProperty', TMyProperty);
end;
procedure RegisterPropertyEditor(PropertyType: PTypeInfo;
ComponentClass: TClass; const PropertyName: string;
EditorClass: TPropertyEditorClass);
begin
if Assigned(RegisterPropertyEditorProc) then
RegisterPropertyEditorProc(PropertyType, ComponentClass, PropertyName,
EditorClass);
end;
So, I call .Register();
which calls RegisterPropertyEditor()
which call RegisterPropertyEditorProc()
which calls RegisterPropertyEditor() <=== aaargh!!
So, what should I have in the body of my RegisterPropertyEditorProc ?
After further searching, it looks like I want to call DesignEditors.RegisterPropertyEditor() directly, but it is not in the interface section ...

There is no point in trying to register a property editor at run-time, as it is not usable at run-time to begin with. It is only usable within the IDE during design-time.

Delphi does not include the source for the DesignEditors unit; its implementation is provided solely in the DesignIDE package. That package has access to IDE internals, such as the list of registered property editors. The IDE assigns values to the RegisterComponentsProc and RegisterPropertyEditorProc callback functions. As you noticed, RegisterPropertyEditor calls RegisterPropertyEditorProc. The IDE provides its own function to handle that event.
If you want to register a property editor at run time, then your program plays the role of the IDE. You need to provide implementations for those callback functions to register the property-editor classes with your own property-editing framework. You could probably just keep everything in a simple list. Then, when you want to know what kind of editor to display for a certain type of property, consult the list to find the best match.
You're correct that you should call your units' Register procedures. But that's how you initiate the registration process, not how you implement it. That part's up to you; Delphi doesn't provide any of this for you.

Related

How to check if unit and function exists?

I'm using Delphi 11. Is there a way to run a specific function inside a TDataModule, but only if this datamodule exists on my project ?
I need a way to check if the datamodule exists and call the function as string, because some projects will have this datamodule added, others not.
Instead of this :
myDataModule.myfunction(param);
Would be something like this imaginary code :
if TDataModule(findcomponent('myDataModule')) <> nil then
TDataModule(findcomponent('myDataModule')).call('myfunction','param');
A lightweight solution could be Messages (those from System.Messaging).
Declare a new TMessage descendant holding the parameters needed for the call
When the datamodule is created it Subscribes to this message type with a handler doing the actions.
Instead of searching for the datamodule just Send the message.
This may or may not be applicable to your situation... But that seems like the kind of situation I would solve with a multicast event.
Your code would call Event.Execute('Param') instead of myDataModule.myfunction(param).
The part of your code responsible for "importing" and creating the datamodule would register to the event. Something like :
EventClient := TEventClient.create(
procedure (const AParam : ParamType)
begin
myDataModule.myfunction(AParam)
end);
EventClient.Observe(Event);
Now, this approach makes sense if the reasons to call your function is "event-like", but this might make less sense in other contexts.
As for library implementing multicast event, I can't recommend any as I always used a proprietary one. If you need inspiration to implement your own, you can look at TMultiCaster in unit Vcl.AppEvnts which implements more or less the same concept.

Possible to change property write methods programmatically using RTTI for creating object-aware controls?

I have a business object that I would like to "connect" to my UI better. I've seen some partial solutions for making objects data-aware, but they all involved significant changes to my business object, including an extra layer of abstraction.
I've been looking into the improved RTTI in new versions of Delphi, and it looks very interesting and useful. I'm wondering if I could use it to programmatically inject new write methods for all properties.
The way this would work is that my TEdit descendant would by given a reference to an object property when the form is built. The TEdit would then insert a reference to itself in an attribute for that property (and of course remove itself on destructor or being given another reference). The TEdit would also ensure that the write method for the property is replaced by one that notifies the TEdit of changes after calling the original write method.
Is this feasible? The big show stopper would be that injecting a new write method isn't possible, hence the title for this question.
There are also potential problems with derived properties, but it should be possible to find a solution for that.
Your question already puts you ahead of me with programming skill so I'll just add how I might approach this:
If I were to try to write something like that I'd probably start with a TList for each field in your TBusinessObject. That list would be used to indicate what needed to be updated when you needed to push out a change.
So when the TEdit is created it would add itself to a list which was associated with a piece of data in your TBusinessObject. When the TBusinessObject updated that piece of data it would then run through the list of attached objects. It would see the TEdit and, knowing it was a TEdit, would run code to update the .Text. If I attached a TCaption then the code would update the .Caption.
The TEdit, as you indicated, would need to tell the TBusinessObject when it's value was updated. I guess this is the tricky spot - you could create a new TEdit and add in a TList to maintain who it should inform when it changes. If you used the .Tag to indicate a field number in the TBusinessObject then the OnChange (or whatever event) could then call something like TBusinessObject.FieldUpdate[TEdit.Tag, NewValue] which then triggers your business logic. That, in turn, might make the TBusinessObject update other fields, which may have their own TLists to fields to update.
Preventing circular updates would require that you have a way of updating a control without triggering events. For one program I wrote I had two methods to update the control: SetValue and ChangeValue. SetValue disabled any events (OnChange, OnValidate), updated the control's value and then reenabled the events. ChangeValue simply changed the value and allowed any of the control's events to fire as required.
There are probably slicker ways to do this but hopefully this gives you food for thought.
Possible to change property write methods programmatically using RTTI for creating object-aware controls?
No, it's not possible. RTTI gives you information, it doesn't give the ability to alter types at runtime.
The big show stopper would be that injecting a new write method isn't possible, hence the title for this question
In order for you to change this at runtime there should be something similar to an event handler that you can set. It's an easy concept, but it has some runtime overhead, both in call time (it would be an indirection where a direct call would normally suffice) and in terms of required memory (each property would require an extra TEvent style field). This is easy for you to implement if you need it, but it would be harmful if the compiler automatically generated such code for all classes "just in case".
If you're thinking of somehow patching the code in memory at runtime, that's not going to work and it would be, at best, unreliable.
In this post entitled Inducing The Great Divide, Cobus Kruger talked about business objects.
The solution he cooked is essentially compliant to your requirements:
Make use of advanced RTTI features introduced in recent Delphi version.
Separation of the business logic from presentation logic.
Any PODO (Plain Old Delphi Object) will do as business object !
The magic lays in the TObjectBinding class which ties any TWinControl with any business object.
Excerpt:
TObjectBinding = class
private
fCtx: TRttiContext;
fControlType: TRttiType;
fObjType: TRttiType;
fPropFieldMapping: TDictionary<TRttiProperty, TRttiField>; // Dictionary of object Properties & corresponding Fields
fControl: TWinControl; // The control (normally form)
fObj: TObject; // Object it represents.
procedure CreateMappings;
function FindField(Prop: TRttiProperty; out Field: TRttiField): Boolean;
function FieldClass(Field: TRttiField): TClass;
// Modify these to change the rules about what should be matched.
function IsValidField(Field: TRttiField): Boolean;
function IsValidProp(Prop: TRttiProperty): Boolean;
// Modify these to change the mappings of property type to VCL control class.
procedure AssignField(Prop: TRttiProperty; Field: TRttiField);
procedure AssignProp(Prop: TRttiProperty; Field: TRttiField);
// Used from AssignField/AssignProp. Extend these to support a wider range of properties.
function GetPropText(Prop: TRttiProperty): string;
procedure SetPropText(Prop: TRttiProperty; const Text: string);
public
constructor Create(Control: TWinControl; Obj: TObject);
destructor Destroy; override;
//
procedure Load;
procedure Save;
end;
I hope that this will be a good starting point for you.

Using another forms event procedure

Is there any way to allow one form to use the event procedures from another form?
E.g. I have a form called PongForm and another called ObstPongForm. There is a ticker on PongForm and another one on ObstPongForm. Is it possible to get ObstPongForm to use the code from PongForm's 'tick' event in it's own 'tick' event? Maybe by letting ObstPongForm inherit from PongForm?
You can simply assign it by code (as long as you have access to both instances):
ObstPongForm.Ticker.OnTick := PongForm.TickerTick;
Yes, forms are just classes like any other, and Delphi supports visual inheritance, so you can call inherited methods normally.
If ObstPongForm is a specialized version of PongForm then inheritance makes sense, but be careful as ObstPongForm will inherit all visual controls from the PongForm, including whatever you may add in the future.
Also since I assume you already have both forms, making one inherit from another is doable but requires some manual DFM editing, mainly changing the
Object ObstPongForm: TObstPongForm
to
Inherited ObstPongForm: TObstPongForm
If the code you want to reuse may be needed in several unrelated forms, then moving the code to a common unit used by these forms may be the best solution
It would be better style to have both of the forms call another class that implements the logic used by both. If you're writing all your program logic in your OnTimer event handler, you're heading down a bad road that many delphi programmers take years to realize was a bad idea
So one form needs to call your method, it does it like this:
procedure TForm1.DoSomething;
begin
DataModule1.LogicMethod;
end;
Elsewhere there is a timer...
procedure TForm2.Timer1Timer(Sender:TObject);
begin
DataModule1.LogicMethod;
end;
And then the method itself:
procedure TDataModule1.LogicMethod;
begin
// Everything that you used to have in Timer1Timer goes here, except the setting of
// UI properties in Form1 which is kept in Form1:
Inc(FCounter);// stupid example.
//
if Assigned(FOnResults) then
FOnResults(Self, FCounter, FDataObject1);
// Form2 is connected to FOnResults event, and stores the
// result in the UI somewhere.
end;
Event handlers are just normal procedures. If your ObstPongForm tick handler has additional code that it needs to run in addition to the PongForm's code, then you can call the PongForm's tick handler manually when needed, eg:
uses
..., PongForm;
procedure ObstPongForm.TickHandler(Sender: TObject);
begin
...
PongForm.TickHandler(Self);
...
end;

Retrieving Delphi Window Handles

I am trying to get the window handles to a Delphi application from an external application. I can see that there are a few windows created (TApplication, TFrmMain and a few others), and I know that TApplication is the "controller", but never visible. However, can I read what the value for the real window is? I know that it is TFrmMain (for this specific application), but is it possible to actually figure this out somehow? Is the information stored in the window properties,or somewhere else? Thanks!
No, there is no documented way to discover which of the windows represents Application.MainForm from outside the application. In newer versions of Delphi, the main form's window handle isn't necessarily Application.MainForm.Handle anyway; applications can handle the OnGetMainFormHandle event to return whatever they want — that's used for choosing the parent window for modal dialogs.
You can guess by looking for windows with "main" in their class names, but even if you find one, there's no guarantee that there's only one instance of it. Applications can have multiple top-level windows, in which case it doesn't make much sense to designate any one of them as the "main" one.
The class name of any Delphi form is also the registered window classname of the underlying "Windows window". So you should be able to use the FindWindow() Windows API call to get the window handle of TFrmMain a little something like:
hWnd := FindWindow('TFrmMain', NIL);
If there are (potentially) multiple instances of a given form class name then you may be able to distinguish between them by using the 2nd parameter (Window Name, i.e. "caption" or title). If that still isn't enough then you may need to get a little bit more sophisticated and look at using the EnumWindows() function and checking the properties of the windows to find the one of interest.
To test the classname of an arbirary window handle (e.g. in your callback function that you use with EnumWindows()), use GetClassName(), e.g:
function GetWindowClassName(const aHWND: HWND): String;
var
buf: array[0..255] of Char; // Tip: Use a more appropriately sized array
begin
GetClassName(SomeHWND, #buf, Length(buf));
result := buf;
end;
...
if SameText(GetWindowClassName(hwnd), 'TFrmMain') then
...
etc
Without specific details of your particular implementation challenge it's difficult to say which is most likely to work best for you, but hopefully that should be enough pointers to get you heading down the right track.
I usually use WinDowse to help me get started, but then you have to use the API functions as described by Deltics.

Is it possible to have unit names added to each call to a function in a different unit?

One of the problems I often run into, is that I will include 'Windows' in my uses clause, and then I will later add 'JwaWinBase' for some specific calls.
However, many of the functions in the 'Windows' unit are the same as in JwaWinBase, and I start getting errors in my main unit, all over the place, until I fix all my calls by pre-pending the correct unit name, like this:
Old:
CreateProcessAsUser(...)
New:
Windows.CreateProcessAsUser(...)
JwaWinBase.CreateProcessAsUser(...)
What I want to know, is if there is a way to have the unit name automatically pre-pended to every call to a function in another unit? This way, before I added JwaWinBase to my uses clause, I could have the 'Windows' unit name pre-pended to any function calls. Then adding JwaWinBase wouldn't give me any errors.
I'm currently using Delphi 2007.
There isn't.
However, the function calls are handled in the reverse order they're in the uses clause, so that if you have this:
uses
Windows, JwaWinBase;
... it will call Jwa functions by default. However, if you reverse them:
uses
JwaWinBase, Windows;
... it should call the Windows functions by default, and you can preface your Jwa functions as required.
If you really need the resolution on a per-routine base, you could try inline forwarders:
procedure Blah; inline;
begin
Windows.Blah;
end;
procedure Blubb; inline;
begin
JwaWinBase.Blubb;
end;
// later:
procedure UseThem;
begin
Blah; // calls Windows.Blah
Blubb; // calls JwaWinBase.Blubb
end;
at the beginning of the implementation section (completely untested :-)).
Maybe it's enough to just switch the order of the two units in the uses clause.
If you use an editor like CodeRush (D7 and before) or Castalia or even the template feature of D2009, you can create templates that expand to what you want AS you enter them. Then you can keep the Windows, jwaWinBase order in the uses section. You can set jcpau to expand to jwaWinBase.CreateProcessAsUser while cpau expands to CreateProcessAsUser or to Windows.CreateProcessAsUser depending on your preference. You'd only have to go through the various functions in jwaWinBase and make templates for them to be safe.
Otherwise, I think it's search and replace on a case-by-case basis.

Resources