Delphi reaching a DBGrid's rows - delphi

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

Related

How to find a table in word and update values in the table using delphi

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

How best to present my Delphi database table to FastReport for lists and aggregates

I have spent several days so far laying the ground work to use FastReport in my Application. The Application stores device test result data in the form of a DBF file comprising several fixed fields(DeviceID, Passed etc) plus a variable number of result fields, each of which correspond to the type of measurement data available. There can be as few as one of these fields and as many as 100. Each field has a letter code name such as OV and RV. Total record counts can be from zero up to some 10's of thousands.
A specific report template will have already included in its design the field names that it will display. Missing fields will be empty on the report.
My question involves the best way of designing the report and the data supplied to the report so that the report construction is as simple as possible - I'm going to allow my users to generate their own reports - and I need two kinds of report output - list of results and aggregates. It is the aggregates that are giving me the headache. I need not only MIN, MAX, COUNT etc (as provided internally in FastReport) but Standard Deviation as well. Further, I would like to use the FastReport 'drill down' feature where you can click on a group header and the data table is revealed or hidden. My aggregates should ideally be in the header, not the footer so that they appear all the time.
I have found that SQL in a TQuery gives me a lot of flexibility since it provides the 'StDev' aggregrate (FastREport does not) but as far as I can see I would need a fixed TQuery for each of my fields. So far, the nicest solution that I can come up with involves using a filter on the main table for 'Passed' (so that the user can view passe, failed or all) and then to build a separate 'stats' table with the same field name columns, but with MIN, MAX, COUNT, MEAN, STDEV as individual records. I would then use a TfrxDBDataSet to expose this table to FastReport. I see that I can also use FastReport's own ADODatabase and ADOQuery to directly access my DBF file. This works well but again I did not want to expose this access layer to my user in the report if possible.
This just seems so messy when aggregate functions must be a fundamental database requirement. Am I missing a much easier way of doing this? I've worked my way through the (excellent) demos supplied with FastReport (professional) and I'm using XE2. I'm also aware of the useful functions in the MATH unit if I need to calculate StDev myself.
I would appreciate any guidance, thanks.
For anything you could calculate in code, lists of array values, aggregate or functional calculation results, I prefer to use the TfrxUserDataSet and implement the TfrxReport.OnGetvalue event.
Although it might initially be confusing, the user datasets simply declare a data set name, and the list of fields available through that data set name and use events which fire to let you "navigate" (first, next record) and declare when you've reached the end of your calculated data. This allows you to build a "generator" or else, just a normal virtual-data-provider set of logic for your calculations.
Here's what my OnGetValue events look like:
procedure TfrmReport.frxReportGetValue(const VarName: string; var Value: Variant);
begin
Value := GetReportValue(VarName);
end;
// INPUT: VarName = '(<GlobalArea."hdReportTitle">)'
// OUTPUT: tableName = 'GlobalArea', fieldName = 'hdReportTitle'
function ParseVar(const VarName:String; var tableName,fieldName:String; var ParenFlag:Boolean):Boolean;
var
paVarName:String;
angleBracketFlag:Boolean;
dotPos:Integer;
fieldQuoteFlag:Boolean;
procedure RemoveOuter(var str:String; initialChar,finalChar:Char; var flag);
var
n:Integer;
begin
n := Length(str);
if n>2 then begin
ParenFlag := (str[1]=initialChar) and (str[n]=finalChar);
if ParenFlag then begin
str := Copy(str,2,n-2);
end;
end;
end;
begin
result := false;
fieldQuoteFlag := false;
paVarName := SysUtils.Trim(VarName);
ParenFlag := false;
tableName := '';
fieldName := '';
RemoveOuter(paVarName, '(',')',parenFlag);
RemoveOuter(paVarName,'<','>',angleBracketFlag);
dotPos := Pos('.',paVarName);
if dotPos >0 then begin
tableName := Copy(paVarName,1,dotPos-1);
fieldName := Copy(paVarName,dotPos+1,Length(paVarName));
RemoveOuter(fieldName, '"','"',fieldQuoteFlag);
result := true;
end else begin
tableName := '';
fieldName := paVarName;
end;
end;
function TfrmProfitAnalysisReport.GetReportValue(const VarName:String):Variant;
var
tableName:String;
fieldName:String;
parenFlag:Boolean;
begin
ParseVar(VarName,tableName,fieldName,parenFlag);
result := NULL;
{ Global Area - Header Values }
if sameText(tableName,'GlobalArea') then begin
if fieldName='hdReportTitle' then
result := GetTitle; { A function that calculates a title for the report }
else if fieldName='hdReportSubtitle' then
result := 'Report for Customer XYZ'
else if fieldName='....' then begin
...
end;
if Variants.VarIsNull( result) then
result := '?'+fieldName+'?';
end;
Well, a lot of questions with a lot of possible answers:
1) About the datasets, I really recommend put them in your application (DataModule or Form) instead of using them inside the report. It will give you more flexibility;
2) You can have one query for each aggregation, but this will affect performance if your data tables grows in tons of records. Some alternatives:
2.1) calculate the values in your FastReport script, but this will also expose the logic to the report;
2.2) Iterate through the record on the Delphi code, and pass the results as variables to your report. Example:
frxReport.Variables['MIN'] := YourMinVariableOrMethod;
frxReport.Variables['MAX'] := YourMaxVariableOrMethod;
2.3) Using a ClientDataSet associated with your query and implement TAggregateFields on the ClientDataSet.
I, personally, prefer the 2.2 approach, with all logic in the Delphi code, which is simple and powerful.

Dbgrid calculation

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.

How to create a QuickReport from the contents of a TStringGrid

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.

Delphi - restore actual row in DBGrid

D6 prof.
Formerly we used DBISAM and DBISAMTable. That handle the RecNo, and it is working good with modifications (Delete, edit, etc).
Now we replaced with ElevateDB, that don't handle RecNo, and many times we use Queries, not Tables.
Query must reopen to see the modifications.
But if we Reopen the Query, we need to repositioning to the last record.
Locate isn't enough, because Grid is show it in another Row.
This is very disturbing thing, because after the modification record is moving into another row, you hard to follow it, and users hate this.
We found this code:
function TBaseDBGrid.GetActRow: integer;
begin
Result := -1 + Row;
end;
procedure TBasepDBGrid.SetActRow(aRow: integer);
var
bm : TBookMark;
begin
if IsDataSourceValid(DataSource) then with DataSource.DataSet do begin
bm := GetBookmark;
DisableControls;
try
MoveBy(-aRow);
MoveBy(aRow);
//GotoBookmark(bm);
finally
FreebookMark(bm);
EnableControls;
end;
end;
end;
The original example is uses moveby. This working good with Queries, because we cannot see that Query reopened in the background, the visual control is not changed the row position.
But when we have EDBTable, or Live/Sensitive Query, the MoveBy is dangerous to use, because if somebody delete or append a new row, we can relocate into wrong record.
Then I tried to use the BookMark (see remark). But this technique isn't working, because it is show the record in another Row position...
So the question: how to force both the row position and record in DBGrid?
Or what kind of DBGrid can relocate to the record/row after the underlying DataSet refreshed?
I search for user friendly solution, I understand them, because I tried to use this jump-across DBGrid, and very bad to use, because my eyes are getting out when try to find the original record after update... :-(
Thanks for your every help, link, info:
dd
Since 'MoveBy's are working for you, use them.
Get a 'Bookmark' before closing the dataset. Do your work, reopen the dataset and then reposition your record on the grid with 'MoveBy's. When you're done, get another Bookmark and compare it with the previous one with DataSet.CompareBookmarks. If the result is 0 fine, if not, only then issue a 'GotoBookmark' for the previous bookmark.
This way, as long as another user have not deleted/inserted records your grid will not seem to be jumpy, and if this is not the case at least you'd be on the same record.
edit: Here's some code sample that should reposition the selected record in the correct place even when there had been deletes/inserts in the dataset. Note that the code omits disabling/enabling controls, and the special case when there are less records to fill the grid for simplicity.
type
TAccessDBGrid = class(TDBGrid);
procedure TForm1.Button1Click(Sender: TObject);
var
BmSave, Bm: TBookmark;
GridRow, TotalRow: Integer;
begin
GridRow := TAccessDBGrid(DBGrid1).Row;
TotalRow := TAccessDBGrid(DBGrid1).RowCount;
BmSave := DBGrid1.DataSource.DataSet.GetBookmark;
try
// close dataset, open dataset...
if DBGrid1.DataSource.DataSet.BookmarkValid(BmSave) then
DBGrid1.DataSource.DataSet.GotoBookmark(BmSave);
Dec(TotalRow);
if GridRow < TotalRow div 2 then begin
DBGrid1.DataSource.DataSet.MoveBy(TotalRow - GridRow);
DBGrid1.DataSource.DataSet.MoveBy(GridRow - TotalRow);
end else begin
if dgTitles in DBGrid1.Options then
Dec(GridRow);
DBGrid1.DataSource.DataSet.MoveBy(-GridRow);
DBGrid1.DataSource.DataSet.MoveBy(GridRow);
end;
Bm := DBGrid1.DataSource.DataSet.GetBookmark;
try
if (DBGrid1.DataSource.DataSet.BookmarkValid(Bm) and
DBGrid1.DataSource.DataSet.BookmarkValid(BmSave)) and
(DBGrid1.DataSource.DataSet.CompareBookmarks(Bm, BmSave) <> 0) then
DBGrid1.DataSource.DataSet.GotoBookmark(BmSave);
finally
DBGrid1.DataSource.DataSet.FreeBookmark(Bm);
end;
finally
DBGrid1.DataSource.DataSet.FreeBookmark(BmSave);
end;
end;
Store the value(s) of your unique key field(s) before closing and reopening the query, then Locate to the record after reopening. DisableControls/EnableControls to prevent screen updates.
Just simple piece of code that came in my mind:
procedure DoRefresh(Dataset: TDataset);
var
bkm: TBookmark;
begin
Dataset.UpdateCursorPos;
bkm := Dataset.GetBookmark;
Dataset.DisableControls;
try
Dataset.Refresh; //refresh dataset if it's open
if Dataset.BookmarkValid(bkm) then
begin
Dataset.GotoBookmark(bkm);
end;
finally
Dataset.EnableControls;
Dataset.FreeBookmark(bkm);
end;
end;
Record position depends much on the sort order of resultset you got from the Query/Table object.
If you don't order at all, the order you get from the server is implementation defined and such, can't guarantee that records come in the same order when reopen the query, even if no changes happened. At least in MSSQL and Firebird, results come in different orders if no Order By clause is used.
As for repositioning, I think that TOndrej solution is the safest one - using the primary key of your resultset to reposition the grid on the right record.

Resources