When, the code that create the components on the form and the code that set their properties, is called? - delphi

If I put a component on the form I don't see any code like MyComp:=TMyComponent.Create in unit code. I think the component is created automatically, but when ? And the same happends with the properties that I configured after I put the component on the form. When they are applied at runtime ?

The properties for a form and all the design time components that live on it are streamed in by the framework during the construction of the form. That process is triggered from the form's constructor, in TCustomForm.Create. The pertinent code in there looks like this:
Include(FFormState, fsCreating);
try
if not InitInheritedComponent(Self, TForm) then
raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
finally
Exclude(FFormState, fsCreating);
end;
The key is the call to InitInheritedComponent. That is a function defined in the Classes unit that does the heavy lifting. In a very broad overview it does the following:
Finds the name of the form class and looks for an RT_RCDATA resource of that name. That resource is the .dfm file.
Once the .dfm resource has been located it is parsed.
The .dfm parsing handles the assignment of properties that you set at design-time in the Object Inspector. For instance the parsing might encounter a line like this: Caption = 'My main form' and it turns that into an assignment of the string 'My main form' to the form's property Caption.
The .dfm file is hierarchical. It contains the properties for the various components and controls that are defined at design-time.
As well as setting the properties of the form's design-time components, the .dfm streaming process also instantiates those components.
In order for all of this to work, the streaming framework relies on RTTI. It knows nothing at all at compile time of your classes and components. Hence the need for RTTI. The streaming framework uses the old style RTTI and in fact that is the reason for the existence of old style RTTI. If ever you wonder about why old style RTTI is the way it is, try to view it from the perspective of having been designed to support streaming.

Information about controls and components as well as their properties that you edit while designing in IDE will be stored in your form .dfm file. Creation of that form in run-time will trigger the process of automatic loading of that .dfm file and all controls and components will be initialized at that time.
This is rather simple explanation of what exactly happens, you can start debugger at form creation line and follow what is happening there, but it is quite longish process and you can easily get lost if you are still learning.
You can find form creation code that Delphi automatically writes in .dpr file of your project.
Application.CreateForm(TForm1, Form1);

Related

How to add a property to TTabSheet such that it can be used at design time with TPageControl

I would like to add "MyProperty" to TTabSheet and work with it at design time. However if I subclass it I fear I will need to also subclass TPageControl, since it internally creates/manages the TTabSheets.
I believe this would require duplicating the TTabSheet management code to reference TMyTabSheet instead of TTabSheet, since it will need to invoke TMyTabSheet.create. This feels like something I will regret when a new version of Delphi updates TPageControl and I forget to update TMyPageControl.
I am also researching "class helpers" as another option.
Does anyone have advice on how to best accomplish this?
Thanks!
Type Helpers are merely compile-time sugar, they would cause no effect over the already compiled code, that stock VCL is together with TPageControl and TForm. That is a dead-end.
However you have a slight misconception here that TPageControl... internally creates... the TTabSheets. Indeed, when you create the sheets by the means of TPageControl itself, like right-clicking it and selecting "New Tab" that si what happens. But when you create the living form object out of DFM file (or DFM resource in your compiled EXE) that is TForm itself that creates ALL the components, including both page control and its tabsheets.
Just see my answer at How to efficiently let a `ParentFont = False` child control to use same font name as parent? - that would show you how far it is about the owner - TForm, not about TPageControl or other parent components when it comes about selecting specific classes for the actual tabs or other elements..
So you are free to pursue subclassing tabsheets only.
Create the TMyTabsheet = class(TTabSheet) component
Create and install into IDE the design-time package that would introduce new subclassed tab sheet to Delphi Form Editor
In your form put the stock TPageControl and create all the needed tabs regular way
in IDE form Editor right-click over the form free space and in the menu do the "View As Text Alt-F12" command - you would see the text content of your form's DFM file
in those DFM sources find your tab sheets and change their stock TTabSheet class to be your derived sub-class
right-click the text editor and choose "View As Form Alt-F12" command
If all was done correct then Delphi would recreate the form with your new-class sheets now. Find your new properties in Object Inspector and change them.
Now switch to .Pas sources of your form and find the declarations of those tab sheets and change their type too. That is only required if you would have to access your new properties from the Delphi sources. If not you can leave their declaration as TTabSheet in pas-file as your class is direct descendant from it. You may leave those declarations as they were - but then you would have to add RegisterClass(TMyTabSheet); call into the very initialization section at the bottom of your unit, so when the form would construct itself out of the DFM it would be able to find the class implementation by the name. If you would change the declaration (at least one of those) then your form would automagically call all needed RegisterClass before streaming out of DFM. Choose any option you like.
Optionally and later, extend your design-time package to find and hijack IDE Form Editor's right-click menu for TPageControl and add "New My Subclassed Tab" command there. Just to avoid manual post-factum DFM editing. if you would do it often
This feels like something I will regret when a new version of Delphi updates TPageControl
After you created and tuned the form and saved it into DFM - it would be TForm that creates all the components out of the saved DFM-data, and that includes your tabs too. Unless very improbable event EMBT would kill the whole VCL streaming (made back in Delphi 1 in 1995) and redesign it from scratch (killing all the compatibility with existing Delphi sources at once), there should be no problem with forward compatibility with specifying your class in DFM. It is just the standard way VCL is designed - to get specific component types from the DFM.

How to debug or fix "Module has open descendants or linked modules" error?

I've had this long time problem that I can't view as text the main form for a project I inherited. Even if no other forms are open.
How can I debug the cause of this error message? What options do I have to fix it?
I found one related newsgroup post http://embarcadero.newsgroups.archived.at/public.delphi.ide/200906/0906193960.html but this only addresses the form inheritance cause, and doesn't explain anything about linked modules. I don't believe I'm using form inheritance.
I do have a DM (data module) for the project, and the form does load a couple of images from the dm through properties of a TTreeView on the form--does having a data module automatically mean I can never view as text a form in Delphi (aside from viewing the form as text in notepad)? It doesn't seem to matter whether my DM is open or closed in the IDE.
I also found one SO question with a related title (Module %s has open descendants or linked modules. can not reload) but the question itself and it's answer is not particularly relevant.
This is sometimes caused by a form that inherits from another form in your project (or the gallery) (known as Visual Form Inheritance in the documentation, IIRC). The IDE doesn't know how to find the base class for the form; it needs that opened before the descendant form. For instance, this can cause the same error if the unit containing TMyBaseForm isn't opened first in the IDE, particularly if the base (ancestor) unit is not included in the project first:
unit SpecialForm;
interface
uses
Forms, { all the other usual stuff }, BaseForm;
type
TMySpecialForm = class(TMyBaseForm)
private
public
end;
You can tell if this is the case by looking at your form's class declaration - if it descends from anything other than TForm, this is probably the cause of the error.
(Another instance of it happening is often when using a datamodule, because the base TDataModule .DFM isn't available. Attempting to view the datamodule .DFM as text will cause this error every time; the solution is to close your project and use an external editor such as Notepad or Notepad++ to edit the .dfm for your datamodule.)
I've had this issue occasionally, perhaps when I've used Frames, but my latest instance didn't involve Frames nor Data Modules nor inherited forms.
After an enormous amount of work creating a copy of the form (which copy didn't have the problem) and renaming the original unit and the form itself (which initially seemed to solve the problem), it turned out to be a live binding between forms.
Specifically, in my FMX application Form A has an options page with a TSpinBox that allows the user to set the minimum value for a TTrackBar on Form B (which was the form giving me grief). So the TSpinBox.Value was set to update the TTrackBar.Min field by means of a live binding. Closing Form A, or removing that live binding (and replacing it with an event handler to do the same thing) solved the problem.
I'd like to call upon the answer of Phillip J. Rayment and ADD that you don't have to have live binding to have this problem occur. It's sufficient to have custom control (class) of which you have an instance in another form. Then the RLink32 problem can appear and won't be solved until you close the form where you have the instance. The problem I experienced gave the following messages:
-RLink32 (during building)
-Access violation in module designide160.bpl` (if I made a modification to the problematic form)
-The module has open descendants or linked modules” error (if I tried to 'View as Form')

Binding a second instance of a form to a second instance of a data module?

I have a Form which has data aware controls bound to datasets in a datamodule. I need to create additional instances of both the form and datamodule in the same application.
I deleted the global variable that was automatically created when the data module was first added to the project. To my delight, controls in the designer could still be bound to datasets in the data module without this global variable. I'm assuming the IDE is parsing the dfm of the datamodule so the designer can still "see" the datamodule. (without the data module loaded in the IDE the list of data sources is empty)
When I create two instances of the form and two instances of the datamodule at runtime both instances of the form appear to be bound to only the first data module that was created. Inspecting the second instance of the data module reveals that the Name property has a number suffix that wasn't there at design time.
The form depends on a lot of datasets in the data module. Is there an easier way to bind the second form instance to the second data module's datasets without resorting to hand coded SomeControl.DataSource := Module2.dsSomeData for every single control?
I'm open to alternative suggestions as well. One solution would be to move the datasets to the form itself. Still it seems a shame if design time data binding only works on singletons.
Take a look at this question:
separate dataset instances using datamodules in delphi
Basically the answer is to create your DataModule, then your Form, then set the name of the created DataModule to an empty string. That will let the initial data binding happen but prevent other forms from seeing that Module.
In addition the next version that is created will still have the original name (no need for the numeric suffix).
I have a Form which has data aware controls bound to DataSets in a DataModule.
That is not possible. Indirectly, ok agreed, but there has to be a necessary DataSource in between. (From the rest of your question we have to distill the information that those DataSources are on the DataModule, but the question could certainly be more transparent about that.)
I deleted the global variable that was automatically created when the DataModule was first added to the project.
Good, stick to that custom!
To my delight, controls in the designer could still be bound to DataSets in the DataModule without this global variable. I'm assuming the IDE is parsing the dfm of the DataModule so the designer can still "see" the DataModule.
Same incorrectness/confusion between DataSource and DataSet, but you are partly right: for the IDE being able to find a DataModule, the following must be true:
The DataModule must be created/opened at least once during the session of the IDE (it may be closed afterwards during the session), see (*),
The unit of that DataModule must be present in the uses list of the Form unit.
When I create two instances of the Form and two instances of the DataModule at runtime, both instances of the Form appear to be bound to only the first DataModule that was created.
That is because you are depending on the automatic design time binding which does not work at runtime. That binding depends on the name of the DataModule. But this is not the only disadvantage of depending on design time binding, see (*).
Inspecting the second instance of the DataModule reveals that the Name property has a number suffix that wasn't there at design time.
Plus an underscore in front of that sequential number suffix. It seems to be by design. There cannot be multiple DataModules (nor Forms) with the same name, which is comparable with Components not capable of having the same name of that of a sibling or child Component. It is a little strange though, because when giving no Owner or even different Owners, the same rule still applies for DataModules and Forms, which is unlike default TComponent behaviour. I cannot find evidence nor an explanation in the RTL/VCL code. Maybe it has something to do with all DataModules and Forms being kept in the Screen variable. We just have to accept, but it is a no-problem.
The Form depends on a lot of DataSets in the DataModule. Is there an easier way to bind the second Form instance to the second DataModule's DataSets without resorting to hand coded SomeControl.DataSource := Module2.dsSomeData for every single control? ... One solution would be to move the DataSets to the Form itself.
Wherein dsSomedata being the DataSource!
Yes, there is an easier way. Instead of placing the DataSets on the Form, place the DataSources on the Form. Typically, a Form often has only a single or few DataSources compared to the number of data controls. That way the data control - DataSource binding remains intact (because both are read from the same DFM), and only the DataSet settings of the DataSources remain to be set manually:
TCustomerForm = class(TForm)
DataSource: TDataSource;
procedure FormCreate(Sender: TObject);
private
FData: TCustomerDataModule;
end;
procedure TCustomerForm.FormCreate(Sender: TObject);
begin
FData := TCustomerDataModule.Create(Self);
DataSource.DataSet := FData.Query;
end;
Or, when you want to create the Form from the DataModule:
TCustomerForm = class(TForm)
DataSource: TDataSource;
private
FData: TCustomerDataModule;
procedure SetData(Value: TCustomerDataModule);
public
property Data: TCustomerDataModule read FData write SetData;
end;
procedure TCustomerForm.SetData(Value: TCustomerDataModule);
begin
if FData <> Value then
begin
FData := Value;
DataSource.DataSet := FData.Query;
end;
end;
(*) In large projects with many Forms and DataModules, it is very common to not open every DataModule in the IDE, and (DataSource.)DataSet settings easily can get lost. Being dependent on the designtime binding by DataModule name then may result in your Forms never showing any data. This is a bug which is hard to foresee whithout checking every Form's (DataSource.)DataSet settings.
Doing it all in the above outlined manner ensures correct DataModule creation at runtime, and ensures all designtime capabilities and cleverness of the IDE providing DataSets, Fields, etc...
I have the same issue. I used the following solution.
My 2 instances of the form share the same DataModule instance. The 2 forms don't show up at the same time. I have the advantage, that the 2 forms show always the same data, since my data is in memory cached, with the TCLientDataSet.
e.g.
_dmSachkonto := TCachedDataModules.Instance.Add(TdmSachkonto) as TdmSachkonto;
TdmSachkonto is my DataModule.

Using classes in dwsunit

1- Why is it necessary to name the methods of a class in a dwsunit this way?
dwsUnitClasses'ClassName''MethodName'Eval(Info: TProgramInfo;
var ExtObject: TObject)
2- Is there a link that must be done between the method defined in a dwsunit and its code?
coz it is not working with me. I took the demo attached with the DWscript (Custom Classes) and made something that looks like it but it is just not functioning.
No it's not necessary, that's just the format the IDE uses for auto-generated event names.
Usually you define script classes, their methods in the structure view, then you double-click their OnEval (or other) event in the properties editor, the IDE will create a method with a name like the above and you can implement there.
If you declare the methods manually, then you also have to attach their events manually, but it's not different than what you end up doing for a button click event (and where you end up with a Button1Click method).
Apart from the implementation code itself, most of the declarations in DWScript can happen at design-time, and are stored in the DFM. The Structure View is convenient for that, but you can also use the property editor only (it was even more convenient in older Delphi versions, when the structure view was sticking to design-time structures, rather than being reused for source structure).

Delphi 2009 creates my components in wrong order

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.

Resources