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.
Related
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);
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.
I've had this problem for years but maybe it is now possible to easilty solve it. I need to lay out a panel with several TEdit controls, each should show, and allow editing of, a published property of a class. Traditionally I would use TEdit (or a numeric derivative from the Raize or Developer Express libraries) and 'wire up' the OnKeyPress and OnExit events, convert between the edit text and the property type etc etc. All as per Delphi 1 (whose big birthday is soon!).
These days we have RTTI and Live Bindings, so ideally I'd like a way of telling a TEdit (or another similar control) about a single published property and the necessary 2-way link would then be established without all the wiring up and conversions. An object inspector does this job of course, but I'd like a more formal custom layout using labelled edit controls. It would be fine to simply cope with integer, float and string, and something like a TDBEdit where the field name was my property name would be great.
I've taken a look at the 'Bind Visually' designer (I have XE3) but I'm on to uncertain ground. Can anyone suggest a means of doing this? Thanks.
The comments above by Ken White and Sir Rufo are good pointers to the use of Live Bindings for wiring up components between each other, but I need to wire up controls to my own object and which is created at runtime. Further digging led me to this excellent article which pretty much does what I want. Jarrod's TBoundObject is intended to be the ancestor for your own objects, but by including an FObject field passed in the constructor and replacing his use of 'Self' by FObject, you can instantiate a standalone 'TObjectBinder' that easily connects various standard controls to published properties.
I have several custom VCL components that do important tasks in their override of the TComponent Loaded() method. This creates a nuisance when creating instances dynamically since the Loaded() method is not called by the Delphi global loader during run-time, like it does for components that were placed on forms/frames at design-time. I also have to place the Loaded override in the public section of the class declaration so whatever code that creates an instance of the component can call it. Finally I have to remember to call Loaded() for dynamically created instances or subtle bugs will creep into the application, a problem that has bit me several times already.
Is there a better solution or approach to this?
If you need to call Loaded in your code you're doing it wrong. If you depend on a third party control that does, then I would fix that person's control. See below for how.
Let me make up a hypothetical example: Suppose I had 5 published properties, which once they are all loaded, can generate a complex curve or even better, generate a fractal, something that takes a long time.
At designtime I want to preview this curve, as soon as it's loaded, but I don't want the curve to be recalculated 5 times during DFM streaming, because each parameter P1 through P5 (type Double) has a SetP1 method, which invokes a protected method called Changed, and rebuilds my curve. Instead I have the SetP1 method return if csDesigning or csLoading are in the component states, and I then invoke Changed once, from Loaded. Clearly I can't rely on property setter methods alone, in all cases, to invoke all changes. So I need Loaded to tell me to do my first generation of some expensive work, that I want to be done 1 time exactly, not N times, where N is the number of DFM properties that would have been loaded that had method set procedures that invoked a method named Changed or something like that.
In your case, at runtime, you should not be relying on Loaded getting invoked at all. You should be instead, having your property set methods call Changed. If you need some way to change multiple properties at once, and then do some expensive thing only once, then implement a TMyComponent.BeginUpdate/TMyComponent.EndUpdate type of method call, and avoid extra work.
I can think of NO useful places where doing something from Loaded makes any sense, except for cases like the ones above, which should be specific to designtime and DFM based class use. I would expect a properly designed TComponent or TControl to properly initialize itself merely by being created in code, and by having its properties set.
So for my hypothetical TMyFractal component, I would do this when creating it in code without it ever having used DFM loading, or invoking Loaded:
cs := TMyFractal.Create(Self);
cs.Parent := Self; {Parent to a form}
cs.Align := alClient;
cs.BeginUpdate;
cs.P1 := 1.03; // does NOT trigger Regenerate
cs.P2 := 2.3;
cs.P3 := 2.4;
cs.P4 := 2.5;
cs.EndUpdate; // triggers expensive Regenerate method .
cs.Show;
// later someone wants to tweak only one parameter and I don't want to make them
// call regenerate:
cs.P5 := 3.0; // Each param change regenerates the whole curve when not loading or in a beginupdate block.
In my TMyFractal.Change method, I would invoke the expensive RegenerateCurve method once, each time any coefficient P1-P4 is modified at runtime, after initial setup, and once exactly when the component is streamed in from DFM, where Loaded is only used to handle the fact that I can hardly expect to do a beginupdate/endupdate in my control like I would have done in the code above.
To avoid singletons and global variables I'd like to be able to pass parameters to a TFrame component. However since a TFrame normally is included on form at design time it is only possible to use the default constructor.
The parent form can of course set some properties in the OnCreate callback after the TFrame has been created. However this does not ensure that a property is not forgotten, and the dependencies are not as clear as using a constructor.
A nice way would be if it was possible to register a factory for creating components while the dfm file is being read. Then the required parameters could be passed to the TFrame constructor when created by the factory. Is there a way of accomplishing this?
Or does anyone have a better solution on how to pass parameters to a TFrame?
All components, including descendants of TFrame, need to be able to be constructed using the constructor inherited from TComponent. Otherwise, they can't be used properly at design time. If the restriction of design-time use is acceptable to you, then you could override that constructor and raise an exception. That would prevent the component from being placed on a form at design time. Simply provide some other constructor that requires other parameters.
Because of the design-time requirement, all components need to be able to exist with some or all of their properties still at their default values. That doesn't mean the components have to do useful things while they're in that state, but they do need to be able to stay in that state indefinitely. It should be OK, for example, to place a component on a form, save the form, and close Delphi, with the intention of resuming the form-designing at a later time. The component should allow itself to be saved and restored, even if all its properties haven't been set up for final use yet.
My preferred option is to enforce the component's rules only at run time. Check that all the properties are set to sensible values before you allow them to be used. You can use assertions to enforce the proper use of your components. Consumers of your classes will learn very quickly if they haven't finished setting up your components on their forms.
I would normally add a public, non-virtual "Initialise" or (Initialize to Americans) procedure which requires all parameters to be provided. This will then set the properties.
Make the properties protected or private if possible, so the only way they can be set is from calling Initialise(AFoo, ABar : integer).
Then in TFormXXX.FormCreate or TformXXX.Create, have:
inherited;
Initialise(foo, bar);
could you create/registercomponent your own tFrame component and
place that on the form - it's create could have anything passed to it.
If a factory could provide the parameters that you need, why don't you just override the default constructor for your frame, and ask the factory-class for parameters?
I usually make my own constructor. I don't like to create frames at designtime anyway.
a) a frame can be created dynamically when required and destroyed when not needed
b) give the frame a public property with either the parameter data type or a data structure and pass the values to the form through the property.
Example:
TAddress - a class to hold the usual elements of an address.
TAddressFra - a frame with the visual controls to display the address
populate an instance of TAddress with values
create an instance of TAddressFra
assign the TAddressFra.address property with the TAddress instance
use the procedure setAddress(o_address : TAddress) to assign the values of the TAddress attributes to the corresponding visual components on the TAddressFra