associating a datamodule with a report at runtime outside the main thread (within a DLL)
Hi all,
I have a thread in a dll which looks at a queue of report request
events and generates the reports (saved as xml files) on demand.
I create the report and the datamodule in the thread:
within the threads execute method I have:
:: // create the report
:: u_report := TppReport.Create( mainwnd );
:: u_report.Name := 'SERVER_EVENT_REPORT';
::
:: // ppBeginBackgroundPrinting; works sort of if uncommented, see below.
::
:: // create the datamodule
:: dmRBuilder := TdmRBuilder.Create( mainwnd );
(the datamodule contains a TADOConnection name conPC4)
Now when the thread wakes and sees a report in the queue it executes
thus (it looks up the type of report from the event and loads
templates using DADE):
:: with u_report.Template do
:: begin
:: with DatabaseSettings do
:: begin
:: DataPipeline := dmRBuilder.plItem;
:: Name := thisRbItemName;
:: NameField := 'ItemName';
:: TemplateField := 'Template';
::
:: end;
:: u_report.Template.LoadFromDatabase();
:: end;
:: u_report.DeviceType := dtXMLFile;
:: DateTimeToString(fileName, 'yyyy-mm-dd-hh-mm-ss-zzz', in_timeStamp);
:: u_report.TextFileName := format('%s%s.xml', [g_XMLFolder, filename]);
::
:: ServerLoadAutoSearch(in_reportEventID, in_itemID, u_report); // ERROR OCCURS HERE!!!!
::
:: u_report.Reset;
:: u_report.ShowPrintDialog := false;
:: u_report.ShowAutoSearchDialog := False;
:: u_report.ShowCancelDialog := false;
:: u_report.Print();
If I have uncommented ppBeginBackgroundPrinting, it runs nearly as
expected but slowly due to issues getting the DLL's synchronize list
to get pumped by the main application- a different issue i'm sorting
out myself currently.
HOWEVER if I am not "backgroundPrinting" then when it gets to
ServerLoadAutosearch I get an error about i need one ADO database
connection per thread.
looking at the stack trace the problem appears to be in
TdaConnectionList.BuildListRunTime (in daDB.pas) where we have:
:: if not(ppInMainThread) then
:: {do nothing, if this is a multi-threaded app then we really don't want it pulling
:: in other datamodules, because those are usually associated with individual
:: threads in a multi-threaded app. }
::
:: else
:: begin
::
:: for liIndex := 0 to Screen.DataModuleCount - 1 do
:: if (aOwner <> Screen.DataModules[liIndex]) then
:: BuildListFromOwner(Screen.DataModules[liIndex]);
::
:: for liIndex := 0 to Screen.FormCount - 1 do
:: if (aOwner <> Screen.Forms[liIndex]) then
:: BuildListFromOwner(Screen.Forms[liIndex]);
::
:: end;
I am not in the main thread, but if background printing is on then
ppInMainThread returns true and the BuildListFromOwner etc code
executes happily, finding the datamodule i have created and loading
its TADOconnection; ie it works.
BUT If not background printing ppInMainThread returns false, and so
the datamodule is not loaded, resulting in DADE not being able to find
its database connection the application error:
"...raise exception EDataErrorwith message
Tdasession.GetdatabaseForName: No TADOConnection object found for
specified name, conPC4'""
I'm sure I should be able to load the datamodule explicitly at the
start, eg something perhaps like
u_report...BuildListFromOwner(dmRBuilder);
but i can't see how to achieve this. I have it working for now by
commenting out the " if not(ppInMainThread) then" and "else" lines so
it forces the load but not happy leaving it this way.
Any help much appreciated.
Thanks,
Chris Were
(Reportbuilder 15.04 Enterprise, Delphi XE6)
I have a thread in a dll which looks at a queue of report request
events and generates the reports (saved as xml files) on demand.
I create the report and the datamodule in the thread:
within the threads execute method I have:
:: // create the report
:: u_report := TppReport.Create( mainwnd );
:: u_report.Name := 'SERVER_EVENT_REPORT';
::
:: // ppBeginBackgroundPrinting; works sort of if uncommented, see below.
::
:: // create the datamodule
:: dmRBuilder := TdmRBuilder.Create( mainwnd );
(the datamodule contains a TADOConnection name conPC4)
Now when the thread wakes and sees a report in the queue it executes
thus (it looks up the type of report from the event and loads
templates using DADE):
:: with u_report.Template do
:: begin
:: with DatabaseSettings do
:: begin
:: DataPipeline := dmRBuilder.plItem;
:: Name := thisRbItemName;
:: NameField := 'ItemName';
:: TemplateField := 'Template';
::
:: end;
:: u_report.Template.LoadFromDatabase();
:: end;
:: u_report.DeviceType := dtXMLFile;
:: DateTimeToString(fileName, 'yyyy-mm-dd-hh-mm-ss-zzz', in_timeStamp);
:: u_report.TextFileName := format('%s%s.xml', [g_XMLFolder, filename]);
::
:: ServerLoadAutoSearch(in_reportEventID, in_itemID, u_report); // ERROR OCCURS HERE!!!!
::
:: u_report.Reset;
:: u_report.ShowPrintDialog := false;
:: u_report.ShowAutoSearchDialog := False;
:: u_report.ShowCancelDialog := false;
:: u_report.Print();
If I have uncommented ppBeginBackgroundPrinting, it runs nearly as
expected but slowly due to issues getting the DLL's synchronize list
to get pumped by the main application- a different issue i'm sorting
out myself currently.
HOWEVER if I am not "backgroundPrinting" then when it gets to
ServerLoadAutosearch I get an error about i need one ADO database
connection per thread.
looking at the stack trace the problem appears to be in
TdaConnectionList.BuildListRunTime (in daDB.pas) where we have:
:: if not(ppInMainThread) then
:: {do nothing, if this is a multi-threaded app then we really don't want it pulling
:: in other datamodules, because those are usually associated with individual
:: threads in a multi-threaded app. }
::
:: else
:: begin
::
:: for liIndex := 0 to Screen.DataModuleCount - 1 do
:: if (aOwner <> Screen.DataModules[liIndex]) then
:: BuildListFromOwner(Screen.DataModules[liIndex]);
::
:: for liIndex := 0 to Screen.FormCount - 1 do
:: if (aOwner <> Screen.Forms[liIndex]) then
:: BuildListFromOwner(Screen.Forms[liIndex]);
::
:: end;
I am not in the main thread, but if background printing is on then
ppInMainThread returns true and the BuildListFromOwner etc code
executes happily, finding the datamodule i have created and loading
its TADOconnection; ie it works.
BUT If not background printing ppInMainThread returns false, and so
the datamodule is not loaded, resulting in DADE not being able to find
its database connection the application error:
"...raise exception EDataErrorwith message
Tdasession.GetdatabaseForName: No TADOConnection object found for
specified name, conPC4'""
I'm sure I should be able to load the datamodule explicitly at the
start, eg something perhaps like
u_report...BuildListFromOwner(dmRBuilder);
but i can't see how to achieve this. I have it working for now by
commenting out the " if not(ppInMainThread) then" and "else" lines so
it forces the load but not happy leaving it this way.
Any help much appreciated.
Thanks,
Chris Were
(Reportbuilder 15.04 Enterprise, Delphi XE6)
This discussion has been closed.
Comments
For a threaded architecture use a DataModule as a container for the Report +
DB Connection. Then for each request, create an instance of the DataModule,
generate the report, Free the DataModule.
TDataModule
TADOConnection
TppReport
The other thing to be aware of is that when a report template loads, the
database name saved with the query is resolved by looking for the
ADOConnection with the same name. The TppReport and TADOConnection must have
the same owner/container which in the above is the DataModule.
Best regards,
-
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
Best regards,
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
interpretation had been that the datamodule and the report had to
share the same owner, ie my ownership structure was something like:
{BEGIN WRONG})
TMyThreadComponent
TppReport
TDataModule
TADOConnection
{END WRONG}
Sorted now though, thanks again.
Chris
On Tue, 13 Jan 2015 11:31:36 -0600, "Nard Moseley \(Digital