Fast Reports Non Database - delphi

I have been using Report Builder for some years, but I am getting tired of the cha-ching, cha-ching. It is a great reporting tool for "non database" reports.
I have started playing around with Fast Reports and I am utterly flustered with it. It seems a great reporting tool for databases but big question mark concerning complex "non database" reports. Their demos and help are horrible.
Wished I could show a report I am talking about. The report is a serial communications report that has operating system information which of course is single in nature. It has 4 distinct table which has installed serial ports and USB Serial Device tables. It also has a summary memo.
Has anyone successfully designed a report of the above configuration in Fast Reports? And yes, I have posted the same query with Fast Reports. Just want other opinions.
Thanks in advance.

I've extended the option provided by #jrodenhi's answer, which seems to be the proper way to do what you want (thus I've made this answer a community wiki). So here are the two options you can use (and I think there will be more of them):
1. Define custom variables
The answer by #jrodenhi shows how to add report variables in code. I'll try to show here, how to define them in report designer.
Open report designer
Go to menu Report / Variables...
In the Edit Variables window create a new category by clicking the Category button. Then you can rename the category the same way as you do e.g. for files in Windows Explorer:
Then you can declare a custom variable by clicking Variable button. You can give to the variable some meaningful name the same way as you do e.g. for files in Windows Explorer:
After this save your changes by clicking OK button:
Then you'll get back to the report designer, where you can find your just declared variables in the Data Tree pane tab Variables from where you can drag and drop the variables to the report:
After you refine all component positions and properties you can close the report designer and go back to the Delphi IDE. There you can write a handler for the OnGetValue event of your report and if its VarName parameter equals to your variable, change its Value parameter to the value you want to give to the associated report component:
procedure TForm1.frxReport1GetValue(const VarName: string; var Value: Variant);
begin
if VarName = 'MyVariable' then
Value := 'This is a new value!';
end;
2. Modify report components directly from Delphi code
There is an option of direct access to report components from your Delphi code. For instance, to find a certain report component by name you can use the FindObject method of the TfrxReport object. Then you can follow the usual pattern of checking if the returned reference is of type of the control you want to access and if so, you can access the instance by typecasting, as usually.
For example to find a TfrxMemoView object of name Memo1 on the frxReport1 report and modify its text you may write:
var
Component: TfrxComponent;
begin
Component := frxReport1.FindObject('Memo1');
if Component is TfrxMemoView then
TfrxMemoView(Component).Memo.Text := 'New text';
end;

Most of the reports I write are for payroll. In addition to the table based data that makes up the bulk of the report, there is usually a significant amount of singular data, such as user and employer information that is frequently most easily entered as a series of variables taken from a filter dialog that runs before the report. Once the filter has run I iterate through all of its variables and add them to the report variables. Maybe you could pick up your singular system variables and do the same.
This is my routine that runs just before I prepare a report. It not only adds variables, it also adds functions defined in Delphi that can be used in the report:
procedure TFastReportDriver.SetVariables(sVariables: String; var frxReport: TfrxReport);
var i,
iDetail: Integer;
sl: TStringList;
sVariable,
sValue: String;
begin
sl := TStringList.Create;
sl.CommaText := sVariables;
frxReport.Variables.Clear;
frxReport.Variables[' Filter'] := Null;
for i := 0 to sl.Count - 1 do begin
sVariable := sl.Names[i];
sValue := Qtd(sl.ValueFromIndex[i]);
frxReport.Variables.AddVariable('Filter', sVariable, sValue);
end;
frxReport.AddFunction('procedure CreateCheck(hPaystub, iCheckNo: Integer)');
frxReport.AddFunction('function NumberToWords(cAmount: Currency): String');
frxReport.OnUserFunction := repChecksUserFunction;
sl.Free;
end;
Then, in your code, you call frxReport.DesignReport and at run time you can drag and drop your variables onto your report:
And, if you have defined any functions like above, they show up under the functions tab.

I've had success with this method:
Use an in-memory table, such as TkbmMemTable, TdxMemTable or any in-memory table.
Link the in-memory table to the TfrDataSet.
Populate the in-memory table using usual TDataset methods and run the report.

You might want to take a look at using NexusDB as a way to hold data for the report. NexusDB is a database which besides being a full client server database can also run as a totaly in-memory database with full SQL support and no external dlls or anything required as it is compiled into your app. They have a free embedded version that will satisfy your needs. I use it for many things, it honestly a wonderful tool to have in your toolbox.

Related

Run-time Equivalent to Assign Local Data... for TClientDataSet and TSQLQuery

The TSQLQuery class is unidirectional, so for it to be used as a source for a data-bound TDBGrid, a TClientDataSet needs to be linked between the TSQLQuery and the TDataSource that the TDBGrid is bound to.
I can connect the TSQLConnection and make the TSQLQuery active at design-time, with specified params, and then I can right-click on the CDS and choose the "Assign Local Data..." option to have it grab the data from the TSQLQuery, which then appears in the TDBGrid via the linked TDataSource.
The amount of time between choosing "Assign Local Data..." and the data actually appearing in the grid is very short, so I am looking for the way to replicate this at run-time.
Supposedly, I can set the Data property of the CDS to the Data of the source, but a TSQLQuery has no Data property. There was a post about using a provider, but
DataSetProvider1.DataSet := SQLQuery1;
ClientDataSet1.Data := DataSetProvider1.Data;
throws an access violation,
I did implement the data copy by looping through the TSQLQuery and appending the records to the TClientDataSet, but that was a lot slower than the "Assign Local Data...".
[Edit 1]
All the components I need are hooked up at design-time, and I can make the TSQLConnection active, then the TSQLQuery, then the TClientDataSet and the TDBGrid displays the data from the parameterised query defined in the TSQLQuery.
In the OnChange event of a TComboBox, I need to refresh the query using a different parameter and have the grid show the relevant results, so I Close the TSQLQuery, change the ParamByName value, Open the TSQLQuery and then call ClientDataSet1.Last to highlight the last row in the grid.
That gives me a "Cannot perform this operation on a closed dataset" error, so I use
ClientDataSet1.Active := true;
but that throws an "Access Violation".
All the examples I can find are all about dropping components onto a form, linking them together, and they work. Well, yes they do, but I need to change properties in code at run-time and still have it work, which it just refuses to do.
This is really beginning to frustrate me.
[Edit 2]
So I followed the example on the Embarcadero site for building a VCL Forms dbExpress Database Application, substituting my database connection details for the Interbase one the example uses.
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Building_a_VCL_Forms_dbExpress_Database_Application
In the designer, everything looked fine and the grid was showing the results from the query, but when I went to run it using F9, I was getting an "Access Violation" thrown from within TCustomClientDataSet.InternalCheck.
Turns out this is a known MIDAS.DLL versioning problem and can be resolved by including MIDASLib in the uses clause of the form. It is just unfortunate that the Datasnap.DBClient code is still throwing Access Violations instead of proper messages, especially since this problem was reported in 2013.
You can use this code. Just change TUniConnection and TUniQuery to what You are using:
Procedure CdsAssignTable(aTableName:String; aCds : TClientDataSet; aCon
:TUniConnection );
Var
aQUery : TUniQuery;
aProvider : TDataSetProvider;
begin
if aCon=Nil then raise Exception.Create('aCon=Nil');
if aCds=Nil then aCds:=TClientDataSet.Create(aCon.Owner);
aQUery:=TUniQuery.Create(Nil);
aQUery.SQL.Text:='select * from '+aTableName;
aQUery.Connection:=aCon;
aQUery.Open;
aProvider:=TDataSetProvider.Create(Nil);
aProvider.DataSet:=aQUery;
aCds.Data:=aProvider.Data;
FreeAndNil(aProvider);
FreeAndNil(aQUery);
End;

How to work on TFDTable(FireDAC) in Delphi XE7?

can some body provide help me to use TFDTable.FireDac is completely new for me. I have used TTable in delphi 2010 for memory database. So i would like to use TFDTable in xe7 for temporary hold.
Well you need to read up on FireDAC generally if you are completely unfamiliar with it, and look at the examples that come with recent versions of Delphi.
But if you just want to know how to copy data from a FireDAC dataset that's connected to a database to an in-memory table, you can do it very simply, like this:
procedure TForm1.btnCopyToMemTableClick(Sender: TObject);
begin
FDMemTable1.Data := FDQuery1.Data;
FDQuery1.Close; // don't need it open any more
end;
Here, FDQuery1 is a TFDQuery, which is similar to a TQuery, in that it has a SQL TStrings property to allow you to specify what Sql query to execute to retrieve the data.

What is the standard way in delphi 5 to watch for a selection change in a DBGrid?

I have many "master/detail" forms in my application. A TDBGrid where each row shows a few core values of the item. Below the grid is usually a "Detail area" that shows the complete information of the item that is currently selected in the grid.
Currently I am listening to the "AfterScroll"-event of the TADOQuery behind the grid, but it seems to give me too many events.
Is AfterScroll the correct event for this? How are you doing this?
The "standard" way (in a data-aware environment) would be to not control that using GUI controls, but rather using the data components.
Most of the table data sets provide MasterSource (linked to an appropriate TDataSource component), and MasterFields properties.
You use these to link your datasets in a master-detail relationship.
Then your detail grid (or other data-aware controls) only needs to concern itself with linking to the correct dataset.
EDIT
Other kinds of datasets (e.g. TQuery, TADOQuery) sometimes provide DataSource to be used for a similar purpose. From Delphi 5 help: "Set DataSource to automatically fill parameters in a query with fields values from another dataset."
However, there are quite a few more complications (as will be observed reading the help). So it may be advisable to use TTable or TADOTable for the detail dataset instead.
I'm not aware if there's any 'standard' way but IMO AfterScroll is fine. Use a timer to prevent updating controls in quick succession, for instance while scrolling the grid. An example:
procedure TSomeForm.DataSetAfterScroll(DataSet: TDataSet);
begin
if not DataSet.ControlsDisabled then begin
if ScrollTimer.Enabled then
ScrollTimer.Enabled := False;
ScrollTimer.Enabled := True;
end;
end;
procedure TSomeForm.ScrollTimerTimer(Sender: TObject);
begin
ScrollTimer.Enabled := False;
UpdateGUI;
end;
I think you'll find an interval of 250-300 ms pleasant.

How to create a report using fast Reports with out connecting directly to a database

I have been asked by my company to update the reporting functionality of a paticular application written in delphi and using Quick reports to use FastReports instead.
The current implementation pulls all the data out of the database, does a lot of work to organise and calculate the required data for the reports and stores all this in several different objects. The Quick Report OnNeedData events are then used to fill out the bands until there is no more data (signified by setting MoreData = false)
The problem I'm having is Fast Reports seems to need a band to be connected to an actual data source, which I don't have. Also fastReports doesn't seem to have an event similar to OnNeedData.
Is there anyway to fill out the values of a data band in code and have it print again until all data is printed without connecting the band to a dataset?
I appologise for the vagueness of this question, I am very new to reporting software and any suggestions of where to go and what to look at would be greatly appreciated.
Fast reports use a intermediary object descending from _TFrxDataSet to connect the report engine which the data it prints.
To connect a report to a data source managed by the program itself, you use a TfrxUserDataSet component, which let's you see a data set inside the report, but you manually specify the column names in the Fields (TStrings) property and manage and supply values programatically writing event handlers for the following events:
OnCheckEOF is functionally equivalent to the OnNeedData, if there's no more to print, you set the EOF var parameter to true
OnFirst you do whatever you have to do to start walking for the data.
OnGetValue and OnNewGetValue you provide values for each of the different columns of the current row
OnNext, OnPrior you move your current row one next or one prior position.
As you see, the row/column concept (a DataSet) is used to provide the data to the report, but you can pull your data from any structure you use to store the result of your calculations (lists, arrays, or any other object/structure/file etc.)
Inside the report, you link the band to this logical DataSet and you use standard components to print the column values of this DataSet.
If you already have the data in a DataSet, for example a in-memory DataSet after your calculations, better use a TfrxDBDataset to directly bind your report to that source of data.
you can use TfrxUserDataSet.There is a demo named 'printstringlist' under folder 'demos'.
In our project we have implemented our own class inherited from TfrxCustomQuery. This new query class simply redirects its SQL statements to our application' internal query engine. We registered this new class in FastReport palette (used frxDsgnIntf.frxObjects.RegisterObject* for FR version 3 and 4) and now it is used in all our report templates instead of TfrxADOQuery or other built-in dataset classes.
Here is another alternative:
I've been using FastReport for years. I sometimes run into a similar situation. For offline tabular reports I use an in-memory dataset. I purchased DevExpress long time ago and so I have TdxMemData. But even without it, you should be happy using the TClientDataset component.
Besides that, the TfrxUserDataset is an alternative which I use when showing lists of objects.
There is a possibility to do it like this, it is slow though ,
Code:-
var
FRX: TfrxReport;
procedure NewPage;
begin
MyPage := TfrxReportPage.Create(FRX);
MyPage.CreateUniqueName;
MyPage.PaperSize := DMPAPER_A4;
end;
procedure ...(AText: string);
var
frMemo : TfrxMemoView;
begin
frMemo := TfrxMemoView.Create(MyPage);
frMemo.CreateUniqueName;
frMemo.Text := AText;
end;
Regards
Herman

Secondary Shortcut does not fire

I am using Delpho 2006. The Scenario:
On the data module I have an ActionList. One of the actions has a shortcut Ctrl+F4 and I want to have a secondary shortcut Ctrl+W. I tried all of the following:
Adding Ctrl+W to the actions SecondaryShortcut list in the IDE.
Adding it in the DataModuleCreate procedure using either
ActFileCloseFile.SecondaryShortCuts.Add('Ctrl+W');
or
ActFileCloseFile.SecondaryShortCuts.AddObject('Ctrl+W',
TObject(Menus.ShortCut(87, [ssCtrl])));
Using both of these methods in the Create or FormShow procedure of the form where it will be used.
The primary shortcut always works, but not the secondary.
When I put the ActionList on the main form instead of the data module, it works by just adding Ctrl+W in the IDE. What do I do wrong?
The most elegant solution found so far is this:
On the form you want to handle the SecondaryShortCut, add this to the OnShortCut event:
procedure TMyForm.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
Handled := dmDataModule.ActionList1.IsShortCut(Msg);
end;
Alternative:
(This is not a real solution, but a workaround.)
Put an action list on the form that has an identical action to the one on the data module. In its execute and update events it only forwards the events to the data module action. The menus on the form use the local action.
In this case it suffices to add Ctrl+W to the SecondaryShortCuts property using the IDE.
Obviously, when the action on the data module changes, I have to change all local actions too.
Not a real solution, but if you create the datamodule from within the mainform it works:
procedure TMainForm.FormCreate(Sender: TObject);
begin
FDataModule := TMyDataModule.Create(self);
TMyButton.Action := FDataModule.TheAction;
end;
procedure TMyDataModule.DataModuleCreate(Sender: TObject);
begin
TheAction.SecondaryShortCuts.Add('Ctrl+W');
end;
I think the shortcuts are handled by the form that has the current focus. So yo will probably get the same problems if you are using them in another form.
Short Answer: Actions short-cuts do not automatically fire across forms and data-modules.
If you follow the instructions in the question, you'll find that not even the primary short-cut fires. This is because a key step has been left out of the instructions. One that will serve to explain why OP experienced the primary short-cut firing and not the secondary one.
If you include the extra steps:
Add a menu to the form.
And link a menu-item to the action.
Then the primary short-cut will be able to fire the action. This is because the Action component pushes its settings to the menu item (including the ShortCut property). However, TMenuItem does not implement the concept of secondary short-cuts. Which is why one works and not the other.
Pause to consider an application with many forms and data-modules; and the implication if action short-cuts could fire across all of them. It should be fairly obvious that they should not be able automatically fire without explicit code to permit it. You wouldn't want a background form doing a bunch of things because its configured short-cut keys happen to be pressed in the context of other unrelated work.
The documentation points out the benefit of putting action lists on data-modules. But doesn't seem to offer any explanation how to use actions with short-cuts on a data-module correctly. Certainly nothing is mentioned in the expected places, namely: ShortCut and SecondaryShortcuts. (I'd be disappointed, but my expectations of decent documentation have been dragged quite low.)
So...
What should be done to get actions with short-cuts working across forms and data-modules?
I have done a bit of investigation and found a few options. As always, evaluate the trade-off's relative to what you are trying to achieve.
When you drop an action list on a (non-main) form, all the short-cuts work as expected. This is the most common scenario and applies when the actions are local and form specific.
When you drop an action list on the main form, all those short-cuts will be able to fire from any other form. This is great for application-wide short-cuts such as opening other forms.
NOTE: There is a prioritisation sequence as to where a short-cut is tested first. So if the active form has a short-cut matching one on the main form, the short-cut will be processed locally. And the main form understandably won't get it.
When a form is tested to see if it handles a short-cut, all owned components are also checked. (This is in fact why the first two above work.) This means that simply setting the Owner of your data-module appropriately will allow its short-cuts to apply to your chosen form.
I.e. Instead of:
Application.CreateForm(TDataModule1, DataModule1);
You can use the following:
DataModule1 := TDataModule1.Create(LocalForm);
However, since each instance of a data-module can have only one owner: you would have to create multiple instances to let multiple forms share the short-cuts. Whether this is an option depends on your circumstances. However, you could also make the main form the owner of your data-module which would be somewhat equivalent to the second option above.
The final option which provides the most control is OP's own answer. I.e. Any form that needs to support "external short-cuts" can handle the OnShortCut event with the following code:
As can be seen in the code sample you can delegate to multiple action lists in different locations according to the priority you choose.
procedure TMyForm.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
Handled := DataModule1.ActionList3.IsShortCut(Msg);
Handled := Handled or DataModule2.ActionList1.IsShortCut(Msg);
Handled := Handled or DataModule1.ActionList1.IsShortCut(Msg);
end;
The action is getting swallowed by the form...If you want a secondary form/frame/datamodule to handle the action...You have to disable the Actionlist from the Primary first...
Form1.ActionList1.State := asSuspended;
DataModule1.ActionList1.State := asNormal;

Resources