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

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.

Related

how to use a private datamodule vs the global instance

I have a datamodule for my frame, which uses a global instance.(dmData)
The data components are linked to the datasources on the dmData instance
now I want to use a datamodule instance that is private to a frame, because I want to have multiple instances of the form which contains the frame showing at the same time.
I can't figure out how to make that happen, either in code or in designing.
in the frame, I am creating the datamodule as dmLocalData := tdmData.Create(self), but in design I don't have the option to link dmLocalData, only the option to link to dmData(so all my data controls are blank (except for that ONE that has a local datasource that gets set in code)
I mean, in code, I could manually go through each component one by one and change the datasource, but thinking there really has to be a better way, the maintenance on that would be pretty much horrendous.
Any ideas about a better way?
Actually there is a way to avoid hand-wiring the controls for a dynamically created datamodule. In short - override the datamodules CreateNew constructor like this:
constructor TMainDM.CreateNew(AOwner: TComponent; Dummy: Integer);
begin
Dummy := -1;
inherited;
end;
This avoids that multiple instances of the datamodule get different names and thus the references are resolved as expected. As the datamodules are private to the frame anyway, there is no need for them to have globally unique names.
A much longer and more detailed explanation can be found in these two articles, which use a quite similar task as an example:
Tweaking DFM Loading (update)
Tweaking DFM Loading

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

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);

How can i work RegisterClass() to units

Base from the anwser How to eliminate variables... i get and accepted it works great when i have all this components and make the actions like a button1.click from the main form...
But i use to make the actions from units... so
When i click a button i great a procedure DoMaths(Sender: TObject);
procedure Tform1.DoMaths(Sender: TObject);
begin
if TButton1(Sender).hint := 'Make the standard Package' then
do_Maths_standard_package;
end;
the do_Maths_standard_package is in unit ComplexMaths.
is the procedure do_Maths_standard_package form unit ComplexMaths it calls some components form Form1... like Form1.label1 etc...
So when i call the RegisterClass(TLabel) and erase the Tlabel from the type it gives an error that it cant find the Label1...
Please can someone help me so not to do the hole program from the start...
Thank you..
You might be able to reference your components like this:
TLabel(Form1.FindComponent('Label1')).Caption := '...';
TCheckBox(Form1.FindComponent('CheckBox12')).Checked := False;
But it's really a pain...
I think you have two options.
1) you can assign each component a unique numeric ID.
And save it into .Tag property.
Just like u use to generate and bind IDs in .HelpContext properties.
Then to get the control by number you would enumerate Form.Controls and get the one with proper Tag value.
The problem would be to have two separate ID lists, in PAS files and DFM files, in sync. Mistyping would be hard to notice. Especially since you have no constants in DFM but only "magic numbers".
2) Set .Name property and use iMan Biglari's recipe - FindComponent by name.
The question is if you can have .Name but not variable. Since no one answers - just try and see.
To my experience - with Delphi 5, hopefully D7 is mostly the same - you can just delete the variable.
If you made wrong declaration of variable, then Delphi editor would notice and ask to correct it.
If you have variable without DFM object, Delphi would notice it and ask to remove it.
But if there is DFM object without corresponding variable, then Delphi editor is oblivious. Maybe it thinks that the object is inherited or whatever.
But if you did not declare it at all it does not mind.
However, since you deleted Names, it looks like it is not possible to you for some reason.
In both cases you would have to cache value if some procedure makes a lot of accesses to some control. And maybe even across the procedures. In effect yu would manually restore those variables at least for most used controls.

"Object Aware" GUI Controls

I have some business objects written in Delphi with a custom scheme of database persistence which is finally working for my needs. Ok, great. Now it's time for GUI implementations. And here begins the problems.
How to bind my object to the GUI properly?
I cannot use Data Aware controls since I isolated all data access components into the ORM layer, so I start to write some "Object Aware" controls using the RTTI unit (I'm working with Delphi 2010), but I have the feeling I'm going the wrong way...
Some ideas on how to resolve this using only the VCL controls?
You have several patterns for linking ORM with User Interface.
See for instance the Model GUI Mediator pattern. In short, you write an observer which will reflect the ORM content into the UI components, and vice versa. This has been implemented for instance in the tiOpf framework for Delphi (this link has videos).
Another approach is to map your data at runtime: you design your form just like usual, then you fill the content in the OnShow event, then the "Save" or "OK" button will validate then save the content into the ORM record. This is what is done in the main Sample application of our framework. Easy to code in this simple sample, but could lead into spaghetti code if you have a lot of fields and validation to operate.
The last approach is to let your ORM creates the form.
In our framework, you can define some UI properties about each table, in a dedicated structure. Then a single unit will create a form with all editable fields of your ORM object. Links to other records will be displayed as a combo box, booleans as checkboxes, sets as radioboxes, and so on. Then the filtering (e.g. trim a text field from spaces on left or right side) and the validation (e.g. ensure that a field value is unique or a valid IP address) is handled not in the UI part, but in the business logic itself, i.e. the ORM.
IMHO it's mandatory to keep a true multi-tier architecture. That is, the UI has to rely mostly on the business logic. For instance, data validation must be part of the ORM, not of the UI. For instance, if you decide to add a web client to your Delphi client application, you won't have to code the validation another time: it will be common to both clients, separated from the UI implementation details.
What you could do (though I have no code samples) is use a combination of
class helpers or interceptor classes
binding interfaces for single domain objects and/or domain object lists
Class helpers have the disadvantage that they are not officially supported and you cannot add any fields to the class you are helping.
Interceptor classes are simply descendant classes with the same name as their ancestor:
uses
stdctrls;
type
TButton = class(stdctrls.TButton)
end;
You can put interceptor classes in their own unit and use that whereever you want. Just make sure these units are included AFTER the standard unit, so your descendant is used at run time.
Benefit of interceptor classes:
You can continue to design your UI using standard VCL or third party controls.
You get all the advantages of descendants.
You do not need to create or install your own controls.
No need for special mapper classes or use of RTTI.
Easily (well, relatively easily) integrated into a (DUnit-) testable user interface along the lines of Julian Bucknall's article on this in the (distinct) Delphi Magazine as referred to in this question/answer: Unit-testing mouse event handlers
Pseudo sample of interceptor control with binding interface / command interface:
uses
stdctrls;
type
ICommandAction = interface(IInterface)
function IsEnabled: Boolean;
procedure Execute;
procedure Update;
end;
IBindSingle = interface(IInterface)
function GetValueFromControl: string;
procedure LoadValueIntoControl(const aValue: string);
end;
TButton = class(stdctrls.TButton, ICommandAction)
protected
function IsEnabled: Boolean;
procedure Execute;
procedure Update;
end;
TEdit = class(stdctrls.TEdit, IBindSingle)
function GetValueFromControl: string;
procedure LoadValueIntoControl(const aValue: string);
end;
implementation could be along the lines of:
function TButton.IsEnabled: Boolean;
begin
Result := Self.Enabled;
end;
procedure TButton.Execute;
begin
Self.Action.Execute;
end;
procedure TButton.Update;
begin
Self.Action.Update;
end;
function TEdit.GetValueFromControl: string;
begin
Result := Self.Text;
end;
procedure LoadValueIntoControl(const aValue: string);
begin
Self.Text := aValue;
end;
My current customer have made their own "mapper" classes in the past (before I came).
Their data objects have fields (which are objects), and you can map these fields to a control. I extended the framework by using a MVC-like approach:
edtTarraCode: TAdvEdit;
procedure TframTarraTab.InitMapping;
begin
...
Mapper.AddMapping(edtTarraCode, Controller.DataModel.tarID);
...
end;
Per control a simple "mapping" class is created:
TMappingAdvEdit = class(TBaseEditMapping)
protected
procedure InitControl; override;
procedure AppData2Control; override;
procedure Control2AppData; override;
end;
No rocket sience, and maybe better solutions are available in the mean time (this worked in D6 and lower :-) ) but it works good enough for the customer.
Btw: also a data object generator is used. So if a field changes in the database (for example tarra.tarid is changed into tareID) we get a compiler error because "tarid" does not longer exist. This works much better than "fixed string" mapping (runtime errors).
There's currently no way to do this using only VCL controls. I know that Lazarus has a set of RTTI-based data-aware controls; you might want to look at them for some basic ideas. But it's more difficult than you might think at first. For example, unlike a dataset, an object has no built-in signaling mechanism when its members' values change. Which means that unless your data-binding controls own the object completely and nothing else has access to it, it's possible for some other code to change some value and then that change doesn't get reflected in the UI.
I've heard various things from the Delphi team in the last few years about extending the object model or the RTTI model to allow for better data binding, but whatever that's about is still a few years out.
Take a look at EverClassy Dataset at http://www.inovativa.com.br. It may meet your needs. EverClassy Dataset is a Delphi dataset designed to be populated by objects instead records from a database system.
With this component you will have the chance to interoperate your domain objects with dataware componentes, what will give you great power to build your GUI.

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