I have some code that works properly at runtime but not in the IDE. I am unable to find any documentation about how the loading of components is different that helps me.
I have developed some components that define resources. I have also developed a custom TDataModule
descendant that holds these resource definitions. As I want to be able to share the definitions between projects I have a simple TComponent
descendent that can embed a TDataModule
within it - the idea being that dropping the appropriate TComponent
on a form or data module includes all of the resource definitions on the embedded TDataModule
.
This appears to be working as expected at run time, but not in the designer.
I will try to explain the operation as concisely as possible. There is lots of code involved but hopefully the following is enough to understand what's happening.
There is a global TDictionary
for registering the resource definitions. The definitions are registered using a Binary UUID (a 16 byte array) as the key. The UUID of the resource is set as a string representation (for ease of streaming). When the string value is set the setter method checks on itself and upwards to see if it or any of the Owner
TComponent
s match csLoading in ComponentState
and if any do the string value is saved temporarily. If not the resource definition registers itself in the global dictionary. (This is necessary because a random UUID is assigned in AfterConstruction
at which point the csLoading
flag is not set and so the parents must be checked).
When a resource definition is fully streamed in the Loaded
call the temporary string is used to set the UUID and the resource is registered.
The TComponent
that includes a TDataModule
of definitions is based on a root class which has the following template code:
type
TITIODataResourcesLoader = class(TDataModule)
...
end;
TITIODataResourcesLoaderClass = class of TITIODataResourcesLoader;
TITIOResourceInclusion = class(TComponent)
protected
_pResources: TITIODataResourcesLoader;
class function _GetModuleClass(): TITIODatResourcesLoaderClass; virtual; abstract;
public
destructor Destroy; override;
procedure AfterConstruction(); override;
end;
destructor TITIOResourceInclusion.Destroy;
begin
FreeAndNil(Self._pResources);
inherited;
end;
procedure TITIOResourceInclusio.AfterConstruction;
begin
Self._pResources:=Self._GetModuleClass().Create(Self);
inherited;
end;
I have component editors for many of the resources which allow selection of a resource by enumerating the Values in the global TDictionary
for that class of resource. So, for example, one of the resource definition types is the definition of a database table and another of the resource definition types is the definition of a database table join. In the component editor for the definition of the join the user can chose which tables to join by selecting any of the tables that are registered in the global TDictionary
.
In a test project that has a TITIODataResourcesLoader
which defines a number of table types, and a TITIOResourceInclusion
that includes another TITIODataResourcesLoader
(which is in a different BPL file) then at runtime the join editor (which is a VCL Form) will show all of the tables, but in the designer only the tables which are defined on the test project's TITIODataResourcesLoader
are available. If I open the TITIODataResourcesLoader
included in the BPL file then the resources on that are registered and selectable in the designer on the test project's TITIODataResourcesLoader
.
So it appears that at runtime the streaming of the TITIOResourceInclusion
does not cause the components in the included TITIODataResourcesLoader
to be registered, but at runtime it does work as expected.
So my question and confusion is: How is the streaming of components different in the Designer?
CodePudding user response:
I asked "How is the streaming of components different in the Designer?"
I added Application.MessageBox
calls to entry points of methods in my TDataModule
derived class and I can confirm that at run time we see the following:
TOuterDM.Create
TInnerDM.Create
TInnerDM.Loaded
TInnerDM.AfterConstruction
TOuterDM.Loaded
TOuterDM.AfterConstruction
Which is a normal order for a component with a DFM file.
However in the Designer we get:
TOuterDM.Create
TOuterDM.AfterConstruction
TInnerDM.Create
TInnerDM.AfterConstruction
TOuterDM.Loaded
Which indicates that instead of calling Create
the Designer is calling CreateNew
for both the Outer and Inner data modules, and then streaming the Outer one but not the inner one. I haven't checked the source code but I expect it's because the designer is only streaming for the 'root instance'
I have managed to get the behaviour I want by adding this code to my TDataModule
derived component:
procedure TITIODataResourcesLoader.AfterConstruction;
begin
// Original code for the deferred load
if(not(csDesigning in Self.ComponentState)) then
begin
// Because this is a component with a DFM file, AfterConstruction is called AFTER
// the streaming is complete
Self._LoadDeferredResources();
// Any 'OnCreate' handler is called AFTER we have initialised ...
end
else
begin
// this code added to stream in the designer
if(Self.Owner is TITIOResourceInclusion ) then
InitInheritedComponent(Self, TDataModule);
end;
inherited;
end;