I am a C++ programmer primarily, I use Embarcadero C++ Builder and often find I have to write things in Delphi. I have some custom components in a package library, some in C++ and some in Delphi. My specific problem is regarding a component written in Delphi.
This component streams properly to the DFM file, but is not including the collection items. The collection items are visible in the IDE and can be set properly.
I have tried including collection items that are working on another component written in C++. The collection items in question are written in Delphi.
The non working component is written in Delphi, all of the other properties appear to work except this collection. The same collection class does stream properly when included in another component (built in C++).
I understand that streaming comes from the TPersistent class and that Ownership is important to have things stream properly (as well as work in the IDE - which it does).
Both of the TComponent objects declare a published property for the TCollection. In the object written in Delphi it's declared as follows:
protected:
{... various other members ...}
function _GetOptions: TITIOOptionChoices; virtual;
published:
{... various other members ...}
property Options: TITIOOptionChoices read _GetOptions;
In the object written in C++ it's declared as follows:
protected:
virtual TITIOOptionChoices * __fastcall _GetOptions();
virtual void __fastcall _SetOptions(TITIOOptionChoices *pNewValue);
published:
TITIOOptionChoices __property * Options = { read=_GetOptions, write=_SetOptions };
The Delphi Component instantiates the collection in it's create method with this code:
constructor TITIOFNCUIStyleMatrix.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{ other property setup code }
Self._pOptions := TITIOOptionChoices.Create(Self);
end;
The working component, written in C++, creates the collection within the AfterConstruction method with the following code:
void __fastcall TITIOFNCOptionChooser::AfterConstruction()
{
// other property setup code
this->_pOptions=new TITIOOptionChoices(this);
}
Both components inherit from TComponent. Both controls allow me to set the collection items correctly in the IDE, but only one of them sucessfully streams the collection to the DFM file.
I have not been able to find any reason why one of my components does stream the contents of the property and the other doesn't. However this suggests that the problem is with how the collection is instantiated as it works within one component and not within another.
What have I missed?
Thanks to the input from the community I have answered the question ...
The Collection property did not have a write accessor specified in the Delphi class.
The IDE did show changes, but (I assume) was not able to write these changes back to the object - and so the object would not stream them.
I had implemented both Assign and AssignTo methods for the collection, I just needed to call the Assign method in the write accessor.
Doh!
Related
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?
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;
Is there any way to get the names of the parameters of a given method of a class/object in an array/delimited string in Delphi 7? Somehow like this:
var
s : string;
aSL : TStringList;
begin
...
// using rtti calls in TMethodUtility.collectParamNames
s := TMethodUtility.collectParamNames( TStringList.addObject );
// or
TMethodUtility.collectParamNames( TStringList.addObject, aSL );
...
end;
Thanks in advance!
It is technically possible (otherwise the IDE's Form Designer could not generate code for component event handlers at design-time), however, there are important caveats that will hinder your goal (and make the particular example in your question impossible to resolve):
the necessary RTTI is only generated for properties that are declared as published (such as component events). RTTI is not generated for methods themselves (even published ones), or for properties that are not published.
TObject has a public MethodAddress() method for getting the memory address of a published method (the DFM streaming system uses this when hooking up event handlers), however you cannot get the necessary RTTI from a method pointer alone.
IF you can match a given method pointer to the value of a published event, then you can extract the parameter names from the event's RTTI. Obtain a TypInfo.PPropInfo pointer for the event using the TypInfo.GetPropInfo() function, then pass its PropType field value to the TypInfo.GetTypeData() function to get a TypInfo.PTypeData pointer, and then you can iterate through its ParamList field (which is an array of records containing ParamName and TypeName fields).
See the following blog article on this topic for more details:
Getting the parameters of published methods.
For what you are attempting, a general-purpose solution would require Extended RTTI that was introduced in Delphi 2010 and thus is not available in Delphi 7. Extended RTTI is not limited to published items, and is much more detailed than what the old-style RTTI provides.
Google is useless for these sorts of searches, because you get hundreds of millions of results absolutely none of which relate to the specific question.
The question is simply this:
Is it possible to have a Class Reference Property in Delphi?
If so, how?
Here's what I've tried...
type
TMyObject = class
// ...
end;
TMyObjectClass = class of TMyObject
TMyObjectA = class(TMyObject)
// specifics here
end;
TMyObjectB =class(TMyObject)
// specifics here
end;
TMyComponent = class(TComponent)
private
FObjectType: TMyObjectClass;
published
property ObjectType: TMyObjectClass read FObjectType write FObjectType;
end;
The above code compiles fine, however the Object Inspector does not show the ObjectType property at all.
My objective here (if you haven't already guessed) is to make it so that I can select a class descendant from a specific base class, to make the same component behave in a different way.
I want to do it this way so that the component doesn't need to know about the sub-classes directly (it needs to be fully modular).
Let me just make this bit clear: I cannot use an Enum to choose between the sub-class types as the component cannot directly link to the sub-class types (It's simply not possible in this particular case)
Anyway... thanks in advance!
You can find all classes that descend from a particular base class: Delphi: At runtime find classes that descend from a given base class? and make this a special property with list of values using TPropertyEditor.
If you were going to do this then you would need to provide a property editor. The IDE does not come with property editors for class type properties. You would also need to handle .dfm persistence. You would write the class type out to the .dfm file as a string and when the .dfm file is read, you would need to fixup the reference. New style RTTI could do that.
However, I don't think any of this is actually viable for the following reason. Your design time code runs in a package inside the IDE and does not have access to the class types in the active project in the IDE. Those class types only exist when that project runs. So the ObjectType property in the code in your question cannot be assigned to anything meaningful in the design time package. Well, you could use it for classes defined in the VCL and any other packages installed in your IDE but I rather imagine you'd want to use it on classes defined in the active project.
I think all this means that you should instead use a simple string property and fixup the class type references only at runtime.
Three components, working together:
* CompA, a TComponent descendant, a mastermind component knowing many things and tying things together
* CompB, a TComponent descendant, mines some data from it's CompA and crunches it. Can amongst other things feed CompC with data to present
- Has a published property of type CompA
* CompC, a TComponent descendant, a TFrame descendant drawing surface that can be set at designtime to use a CompB as data provider
- Has a published property of type CompA
- Has a published property of type CompB
I think I remember having read, even though I cannot state where, that Delphi's streaming engine reads all components from the .dfm and builds a dependency graph. This graph is then used to create all components in correct order. For the listed components it should be CompA first (since it uses none of the other ones), then the CompB (it uses CompA and must be created after) and lastly the CompC since it has properties of both the other component types.
This does not happen. CompC is created before CompB. If i rearrange the order in the .dfm file using a text editor it works. The property values are not used in any constructors, only in the Loaded procedures. But truly there must be a way to make it work no matter the order of components in the dfm?
I've been banging my head against the wall for two days straight now, I need somebody to tell me which keyword I forgot or what error in design I have.
I suspect your fault is you're trying to access other objects properties on setters for sibling pointers, forgetting that at dfm loading stage --runtime-- you can't be sure pointers to other components your component depends on are yet valid because it is possible that other component is not yet created. This works this way since Delphi 1.
Because of this, you usually deffer the reading of other component's state (for example) to your overridden Loaded method.
When the streaming system loads a form or data module from its form file, it first constructs the form component by calling its constructor, then reads its property values from the form file. After reading all the property values for all the components, the streaming system calls the Loaded methods of each component in the order the components were created. This gives the components a chance to initialize any data that depends on the values of other components or other parts of itself.
Note: All references to sibling components are resolved by the time Loaded is called. Loaded is the first place that sibling pointers can be used after being streamed in.
Because of this, usually on a setter method for a sibling pointer property you usually perform a check of this type:
procedure TMyComponent.SetDataSource(Value: TDataSource);
begin
FDataSource := Value;
//streaming in stage
if not (csLoading in ComponentState) then
ReadDataSourceProperties;
end;
procedure TMyComponent.Loaded;
begin
ReadDataSourceProperties;
end;
Take a look at the VCL source, you'll find hundreds of examples of this.
If your components are that much dependent on creation order, you are always going to be in trouble relying on the streaming mechanism. Just one addition or removal of a(n other) component on the form/datamodule can throw your order out of whack.
To ensure proper creation order, you'd be better off creating them at run time. Just note that when you create components at run-time the Loaded method will not be called. You will either have to do it yourself or move the code to some init method that you call after you create your components.
You can right click a form/datamodule and select the "Creation order" item. It will allow you to select the creation order of "non visual" components. Visual ones should follow the tab order, but I am not really sure about that.
Update: I was wrong about the tab order, but it looks the visual controls are streamed to the .dfm in Z-order. If the controls are instantiated following the order they are in the .dfm, you can use Edit -> Bring to front/send to back (or the Control menu in the form context menu) to change the z order. As long as the controls do not overlap you should be enough free to change it.
Please could someone explain me what's the difference between public and published class members in Delphi?
I tried to look at Delphi help and I understand that these members have the same visibility, but I don't understand very well how they differ and when should I use published members instead of public ones.
Thanks a lot.
The compiler generates RTTI (Run-Time Type Information) metadata for published members, but not for public members (by default). The main effect of this is that the published properties of an object will appear in the Object Inspector at design time.
I do not know if you are writing components, but if you do, you probably know that properties and events are normally published, so that they can be set using the Object Inspector.
Public
public
property MyProperty: integer read FMyProperty write FMyProperty
MyProperty will not be visible in the Object Inspector.
Published
published
property MyProperty: integer read FMyProperty write FMyProperty
MyProperty will be visible in the Object Inspector.
Public properties and published properties have the same visibility, as you already stated. Published properties are included in RTTI, public properties aren't.
As a side note, there is another special thing with published:
The default visibility of class members is published, so check for unsafe code like:
TTopSecret = class(TObject)
Name: string;
Password: string;
function DecryptPassword(const AValue): string;
public
constructor Create(const AName, AEncryptedPassword: string);
end;
Name, Password and DecryptPassword() are visible 'world-wide'.
Published properties will export Runtime Type Information (RTTI).
Have a look here about RTTI in Delphi
It seems there are lots of good answers already, pointing out the Object INspector, RTTI,
etc. These are all pieces of the puzzle.
If you take away the published keyword, the entire Delphi RAD tool design would require some way to specify which properties are stored in a DFM, inspected in the component property inspector, and can be reloaded at runtime from a DFM when the form or data module is created.
This, in a word, is what Published is for. It is interesting to me that the designers of QT (originally TrollTech, later part of Nokia, later still spun off to Digia) had to emulate this level of RTTI for their C++ RAD library "QT", adding a "published" equivalent and a "property" equivalent, while pure C++ still lacks this fundamental facility.
Runtime Type Informations (RTTI) are only generated for published class members.
At run-time, entries in the published and public sections are equally accessible.
The principal difference between them is that published items of a component appear in the Object Inspector at design-time.
This happens because, for fields in published section RTTI is automatically generated.
The Object Inspector picks this up and uses it to identify what to add to its list of properties and events.
In addition to the other answers:
Published properties are automatically stored by the streaming system.
For instance if you have a TComponent's descendant instance and write it to a TStream with WriteComponent, all (well, not all, but that is another question) published properties are written to the stream without any further coding.
Of course, the streaming system only can do that because the RTTI is available for those published properties.