I'm using Delphi 7 and QuickReports on Windows 7. Normally QuickReports require a DataSet generated by a query, but I want to make a report from the contents of a StringGrid as though the StringGrid is a representation of the results of a query.
How?
Use the QuickReport.OnNeedData event handler. It passes a var parameter called MoreData (a boolean); setting it to True means it gets called again. Leave the QuickReport.DataSource property blank, and use plain TQRText controls rather than TQRDBText.
// CurrLine is an Integer. In your case, it can represent a row in the StringGrid.
procedure TPrintLogForm.QuickRep1NeedData(Sender: TObject;
var MoreData: Boolean);
begin
MoreData := (CurrLine < StringGrid1.RowCount);
if MoreData then
begin
qrTextLine.Caption := StringGrid1.Cells[0, CurrLine];
qrTextData.Caption := StringGrid1.Cells[1, CurrLine];
Inc(CurrLine);
end;
end;
I assume the set of columns is fixed within the StringGrid (and withing the corresponding TClientDataSet). Step-by-step instructions:
Drop a TClientDataSet on the form
Doublic-click on the TClientDataSet, hit the INSERT key on the keyboard to add an new field, add one field for each of your grid's columns. Example: COL1, String, 128 width.
Right-click on the TClientDataSet on the form and hit "Create DataSet"
AT RUNTIME run this kind of code:
CS.Append;
CS['COL1'] := 'Whatever';
CS['COL2'] := 'An other thing';
CS.Post;
You'll need to do the Append/Post in a loop, looping over each row in the grid. You can assign the COL1, COL2 etc in an other loop, or you can hand-code it.
Related
which run first, TDataSet OnFilterRecord or OnCalcFields event? Are records set to Accept = false are still visible in OnCalcFields event? if it is, is there a property to check record visibility?
the code situation is like, when dataset has more records like 3k, OnRecordFilter has manual filter on string fields for records visibility on grid (Accept = true / false), OnCalckFields has extra columns lookup to other datasets,
the function that sum the amount columns is so slow with or without filter.
when i disable the OnCalcFields event, the execution was so fast.
DataSet is TFDQuery, loaded initial data is free date range so user can view like 3 or more year date range.
ui looks like this
https://i.stack.imgur.com/0XyrN.png
You can test this for yourself.
Create a new VCL project and add a TFDMemTable, TDataSource, TDBGrid, TCheckBox (called cbUseFilterExpr) and TButton
to the form.
Connect up the FDMemTable, TDataSource and TDBGrid as you normally would.
Add an OnFilterCalls integer form field and event handlers shown below.
Compile and run.
Code
procedure TForm1.FormCreate(Sender: TObject);
var
AField : TField;
i : Integer;
begin
AField := TIntegerField.Create(Self);
AField.FieldName := 'ID';
AField.DataSet := FDMemTable1;
AField := TStringField.Create(Self);
AField.FieldName := 'Name';
AField.DataSet := FDMemTable1;
FDMemTable1.CreateDataSet;
for i := 1 to 100 do
FDMemTable1.InsertRecord([1, 'Name' + IntToStr(i)]);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
OnFilterCalls := 0;
FDMemTable1.Filtered := False;
if cbUseFilterExpr.Checked then
FDMemTable1.Filter := 'Name= ''Name1'''
else
FDMemTable1.Filter := '';
FDMemTable1.Filtered := True;
ShowMessage('OnFilterCalls ' + IntToStr(OnFilterCalls));
end;
procedure TForm1.FDMemTable1FilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Inc(OnFilterCalls);
end;
The app populates FDMemTable1 with 100 records. The OnFilterCalls variable will
count the number of times OnFilterRecord is called when filtering is activated.
Clicking Button1 sets a filter on FDMemTable1 which differs depending on whether
cbUseFilterExpr is checked or not: If it is, filtering uses a filter expression which only
matches record ID=1. The result displayed by ShowMessage is 1, iow, the OnFilterRecord
event is called only once. If cbUseFilterExpr is not checked ShowMessage displays the value 100.
Conclusion: For FDMemTable (and, I confidently predict, other FireDAC dataset types) the OnFilterRecord
event is called once for each record which matches the FDMemTable's Filter expression, if any,
or once for each record in the dataset if the Filter expression is blank. Iow, OnFilterRecord
is only called for records which match the Filter expression, if there is one, so it behaves as if OnFilterRecord
is called "after" filtering via the Filter expression, so the answer to your q in FireDAC's case is "No", expression-filtered records are not visible in the OnFilterRecord event.
As mentioned in a comment, TDataSet does not define how a dataset processes filtering, rather it is
implementation-specific and may differ between different dataset component libraries.
Update You still haven't provided any details of what exactly you are doing in your code
(and on reflection your q should probably have been closed for lacking debugging details), but
I think you can satisfy yourself that what I have said above also applies to your situation. Simply put a debugger breakpoint
on the end in TForm1.FDMemTable1FilterRecord(DataSet: TDataSet. Run the app and check the cbUseFilterExpr checkbox. When the bp triggers, repeatedly single-step the debugger by pressing
F8 until you land in the unit FireDAC.DatS, in the method TFDDatSView.Rebuild. You will see that your are in a for loop,
for i := iBegin to iEnd do begin
...
This is the loop which is executed when the filtering is applied to the dataset, once for each record in the dataset, and from the for-loop's contents
it will be a straightforward matter to satify yourself that the OnFilterRecord event is only called
for any record which is visible because it satifies any filter expression which is in effect.
Here's workaround i did to this OnCalcEvent difficulty while no exact answer to my query.
solution:
a way to skip OnCalcEvent when traversing dataset with huge initial records loaded
create a public variable
set value to public variable before dataset iteration
in OnCalcEvent, check the value to exit to abort the event code execution
after dataset iteration. re-set the dataset bookmark to force run the OnCalcFields event else calcFields column will be empty
it works for me, for now.
another idea is to create pagination (paging to limit query return similar to web apps) instead.
I am new to Ole word automation using Delphi. I have a sample document which have many tables inside it. I am able to insert an image by finding a shape in word and insert values inside it. But I am not able to find a particular table and update some values into it using delphi. Is there a way? Thanks !
I assume you're asking mainly how to find the table, rather than how to change the contents of the table afterwards. How to do this depends on the criteria you want to use to find the table of interest.
On the face of it, you should be able to navigate to a given table using the Goto method of MS Word's Selection object. However, there is a problem with that (see at the end of this answer) in detecting when the operation has failed because Goto didn't locate the correct table.
If the table of interest is preceded in the document by an identifying text label, you could simply search for the label and, if found, navigate forwards from that, like this example which finds the table after the label 'Table3':
procedure TForm1.Button4Click(Sender: TObject);
var
AFileName : String;
MSWord,
Document : OleVariant;
Found : WordBool;
begin
AFileName := 'd:\aaad7\officeauto\Tables.Docx';
MSWord := CreateOleObject('Word.Application');
MSWord.Visible := True;
Document := MSWord.Documents.Open(AFileName);
MSWord.Selection.Find.Text :='Table3';
Found := MSWord.Selection.Find.Execute;
if Found then begin
MSWord.Selection.MoveDown( Unit:=wdLine, Count:=1);
end;
end;
As written, the "if Found ..." block merely places the cursor on the first character of the first cell of the table. Once in the table, you can alter its contents however you like.
If you want to find out how to do something like insert an image in a table cell, go to the Developer tab on Word's ribbon, record a macro that does what you want and then use Edit from the Macros pop-up to look at it - it's usually fairly easy then to cut'n paste it into Delphi and edit it into the equivalent Delphi code. Same goes for other methods of finding the table you want - record a macro then translate it.
To find the Nth table in a document and plant the cursor in its top left cell, you can do this:
procedure TForm1.Button2Click(Sender: TObject);
var
AFileName : String;
MSWord,
Document,
Tables,
Table : OleVariant;
TableNo : Integer;
begin
AFileName := 'd:\aaad7\officeauto\Tables.Docx';
MSWord := CreateOleObject('Word.Application');
MSWord.Visible := True;
Document := MSWord.Documents.Open(AFileName);
TableNo := 3;
Tables := Document.Tables;
if TableNo <= Tables.Count then begin
Table := Tables.Item(TableNo);
Table.Select;
MSWord.Selection.MoveLeft( Unit:=wdCharacter, Count:=1);
end;
end;
Btw, in Word's Find dialog, on the Goto tab, there is Table entry in the Go to what listbox. You can call that in code using something like
MSWord.Selection.GoTo(What:= wdGoToTable, Which:=wdGoToFirst, Count:=3);
The problem with it is how to check in code whether it succeeded. Unlike Find, which returns a WordBool, Goto returns a Range object. If you try to use it to go to the 10th table in a document which only contains 2 tables, there is no error raised, but the returned range is the last table in the document. I haven't yet found a way to check from the returned Range whether Goto succeeded without checking some text associated with the table which could have been found using Find in the first place. Of course, if the document is guaranteed to contain the table you're looking for, this problem with Goto probably needn't concern you.
Maybe something like:
Word.ActiveDocument.Tables.Item( 1 ).Cell( 1, 1 ).Range.Text := 'some text';
So i have a TDBGrid, my purpose is searching DBGrid's Fieldname and comparing it with my Edit's Text property and if they are equal then,
i want to write the whole column which i've found the match, to a ListBox.
With a for loop with fieldcount, i can compare FieldName, though since there is no rows or rowcount property i can use, i don't know how i would get the index of this whole column.
for i:=0 to DBGrid1.FieldCount-1 do
begin
if DBGrid1.Fields[i].FieldName=Edit1.Text then
for j:=1 to DBGrid1.RowCount-1 do
ListBox1.Items.Add(DBGrid1.Rows.Fields[i].Index.AsString);
end;
This is an imaginary code of what im trying to do...
P.S.:I'm still using Delphi 7, (educational reasons)
You can't get the row values directly from the DbGrid. Instead, you have to navigate through the dataset that's used to feed the DbGrid.
This example assumes you are using a TClientDataSet.
for i := 0 to DBGrid1.FieldCount - 1 do
begin
if DBGrid1.Fields[i].FieldName = Edit1.Text then
begin
ClientDataSet1.DisableControls;
try
ClientDataSet1.First();
while (not ClientDataSet1.Eof) do
begin
ListBox1.Items.Add(ClientDataSet1.FieldByName(Edit1.Text).AsString);
ClientDataSet1.Next();
end;
finally
ClientDataSet1.EnableControls;
end;
end;
end;
As far as DBGrid only displays an excerpt of the data, IMHO you should
get a bookmark of your dataset
disable Controls
use first, while not eof with your dataset, adding
Dataset.FieldbyName(Edit1.text).asString to your list
goto bookmark
enable controls
I have an app that uses ClientDataSets and local file storage. Some of the info is displayed in a DB grid, and I found it was getting cut off--the first 500 characters or so of a string were displayed, but the underlying fields needed to be longer. So I went back to my code and increased the size of the underlying FieldDefs and also their display widths. I also created a new dataset (at design time). Finally, I ran the application, and created a new database. Nevertheless, only ~500 chars were displayed.
Where should I look to see what's limiting my field lengths?
I'd suspect you're running into an internal limitation of TDBGrid column widths, since a column that displays 500 characters would be more than a typical screen wide. (You can check this if you want to wade through the VCL source; start with Grids.pas.)
Typically, a TDBGrid doesn't display a lot of text in a single column. It leads to a lot of scrolling by the user, and horizontal scrolling to read wide text is extremely annoying to most people.
The usual way of doing this is to use an ftMemo type field, which can contain virtually unlimited text content. When the column is assigned to a TDBGrid, the grid column displays (MEMO), and the application handles a click or double-click on the column to display a secondary form with a TEdit or TRichEdit to display/edit the full contents of the column.
Here's an example of a TDBGrid attached to a TClientDataSet named CDS with the following columns defined (useless, but an example) using CDS.FieldDefs in the Object Inspector:
Column Persistent Name FieldType Size
------ --------------- --------- ----
ID CDSID ftInteger 0
Name CDSName ftString 25
Notes CDSNotes ftMemo 0
Since the underlying dataset doesn't exist, I assign a FileName, and use the following code to create it at runtime:
procedure TForm1.FormCreate(Sender: TObject);
begin
if not FileExists(CDS.FileName) then
begin
CDS.CreateDataSet;
CDS.Active := True;
CDS.InsertRecord([1, 'John Smith', 'This is some longer text'#13'for testing.']);
CDS.InsertRecord([2, 'Fred Jones', 'A note about Fred goes'#13'here for now.']);
CDS.Active := False;
end;
CDS.Active := True;
end;
I put a TDataSource on the form, and set it's DataSource to CDS. I added a TDBGrid, and set it's DataSet to DataSource1.
In the Object Inspector, choose the TDBGrid, go to the Events tab, and add the following to the OnCellClick event (just for display, of course):
procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
if Column.FieldName := 'CDSNotes' then
ShowMessage(Column.Field.AsString); // Display other form here instead
end;
Here's the display after clicking the CDSNotes column on row 1 of the TDBGrid:
If you must display partial content of a longer text column in the TDBGrid, you can use something like the following:
procedure TForm2.CDSNotesGetText(Sender: TField; var Text: string;
DisplayText: Boolean);
begin
// Again, a trivial example using an arbitrary chunk of the first 20
// characters just for demo purposes.
if DisplayText then
Text := Copy(Sender.AsString, 1, 20)
else
// Not for display only; return all the text.
Text := Sender.AsString;
end;
Doing so with the sample app above changes the display to this:
I have a DBGrid and I´m trying to do a billing sheet but sometimes it doesn't do the calculations. How can I avoid that??
procedure TOrcamentos.DBGridEh1ColExit(Sender: TObject);
var
percent: double;
Unid: double;
tot: currency;
vaz: string;
begin
if Dorcamen_SUB.DataSet.State in [dsEdit, dsInsert] then
try
Dorcamen_SUB.DataSet.Post;
finally
vaz := DBGridEh1.Columns[3].Field.text;
if (vaz<> '') then
try
Torcamen_SUB.Edit;
Unid := (Torcamen_SUB.FieldByName('QT').AsFloat);
tot := (Torcamen_SUB.FieldByName('Precovenda').AsFloat);
percent := (Torcamen_SUB.FieldByName('Desconto').AsFloat);
try
tot := tot+(tot * percent)/ 100;
finally
Torcamen_SUB.FieldByName('Total').AsFloat := unid*tot;
Torcamen_SUB.Post;
Orcamentos.TotalExecute(self);
end;
except
end;
end;
end;
The better way to implement calculations is actually to move the calculation to your TTable component that the grid is linked to. The Total field shouldn't actually be a field in the database since but rather a calculated field based on values from other fields. Simply add an extra field using the field editor of the table, type in the field name Total, select the correct datatype and then select the field type as Calculated. Click Ok and then add code similar to this for the OnCalcField event of the table:
Torcamen_SUB.FieldByName('Total').AsFloat := Torcamen_SUB.FieldByName('QT').AsFloat * (
Torcamen_SUB.FieldByName('Precovenda').AsFloat + (Torcamen_SUB.FieldByName('Precovenda').AsFloat * Torcamen_SUB.FieldByName('Desconto').AsFloat)/100) ;
A general rule of thumb is that calculated values shouldn't be saved to the database unless really really necessary. It's best to simply add them as calculated fields to the dataset and then link the grid to the dataset. All calculated fields will then be displayed in the grid and each row will show the correct calculated value based on the values for that row.
I think you're mixing a business logic (like calculating a total) with User Interaction logic (like the event on which some grid column loses the focus) and that's the source of the erratic behavior of your application.
Looks like not even you know where it happens and where it doesn't happen.
Consider using the Field's events (for example, OnChange event) to perform that kind of calculations.
Lucky you if you're using a dataset with aggregation capabilities (like TClientDataSet), because you can just declare what you want in a TAggregateField and forget about doing calculations "by hand".
Not your question but... be careful with the way you're using try/finally also... for example, in this bit of code:
try
tot := tot+(tot * percent)/ 100;
finally
Torcamen_SUB.FieldByName('Total').AsFloat := unid*tot;
//other things
end;
be aware that if for some reason an exception occurs on the line between the try and finally clauses, the variable tot will have an undefined value (in this case, the result of the previous assignment), so the Assignment to the Torcamen_SUB.total field will be wrong, after all. I'm not sure if it is really what you want.