Home > other >  Differences in component streaming at runtime and design time
Differences in component streaming at runtime and design time

Time:02-21

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 TComponents 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;
  • Related