How to print multi selected rows of dataset (Delphi, Fast report) - delphi

Delphi 10.4.2, Fast Report 6.
I use TFDQuery and TDbGrid components.
TDbGrid options: RawSelected,MultiSelected, AlwaysShowSelection=true
RangeBegin and RangeEnd of FrxDbDataset=rbCurrent.
If I select, for example, 3 rows (left ctrl+mouse) then SelectedRows.Count gets value 3 (as breakpoint shows) but is printed columns of only one row (last in numbering selected row in dbgrid). How to print columns of all selected rows? Is it possible without memtable or clientdataset?
query1.sql.text:='select * from datailtable where mastertable_id=:id';
query1.open;
dbgrid1.SelectedRows.CurrentRowSelected:=true;
for i := 0 to DBGrid1.SelectedRows.Count-1 Do Begin
DBGrid1.DataSource.DataSet.GotoBookmark(DBGrid1.SelectedRows.Items[i]);
end;
frxReport1.LoadFromFile(ExtractFilePath(ParamStr(0))+'Doc_id.fr3');
frxReport1.PrepareReport;
frxReport1.ShowPreparedReport;
UPDATE. solution:
var str, sSql, s: string;
Query1.filtered:=false;
sSql:='select * from datailTable where Master_id=:id';
for i := 0 to DBGrid1.SelectedRows.Count-1 Do Begin
DBGrid1.DataSource.DataSet.GotoBookmark(DBGrid1.SelectedRows.Items[i]);
s:=s+','+inttostr(DBGrid1.DataSource.DataSet.FieldByName('ID').AsInteger);
end;
delete(s,1,1);
str:='ID in('+s+')';
Query1.filter:=str;
Query1.filtered:=true;
frxReport1.LoadFromFile(ExtractFilePath(ParamStr(0))+'Doc_id.fr3');
frxReport1.PrepareReport;
frxReport1.ShowPreparedReport;
RangeBegin must be rbFirst, RangeEnd- reLast.

Related

How to empty bookmarklist of selected rows (delphi)

I use this code for multi selecting rows in dataset. It works fine but if I execute it second, third etc time new rows are added to last rows. How empty bookmarklist of last selected rows?
var
sql: string;
i:integer;
query1.close;
query1.open;
query1.Filtered:=false;
for i := 0 to dbgrid1.SelectedRows.Count-1 Do Begin
dbgrid1.DataSource.DataSet.GotoBookmark(dbgrid1.SelectedRows.Items[i]);
s:=s+','+inttostr(dbgrid1.DataSource.DataSet.FieldByName('ID').AsInteger);
end;
delete(s,1,1);
sql:='ID in('+s+')';
query1.Filter:= sql;
query1.Filtered:=true;
frxReport1.LoadFromFile(ExtractFileDir(ExtractFileDir(ExtractFileDir(ExtractFilePath(ParamStr(0)))))+'\FastReports\Labels_DocOut_id.fr3');
frxReport1.PrepareReport;
frxReport1.ShowPreparedReport;
I tried:
query1.close; query1.open;
DBGrid1.SelectedRows.CurrentRowSelected := false;
dbgrid1.DataSource.DataSet.delete;
DBGrid1.SelectedRows.clear;
DBGrid1.SelectedRows.delete;
but without success.

Delphi TListView Trouble

I have a TListView on my form. I add some columns in it depending on the input like so:
MyItem := StringListView.Columns.Add;
MyItem.Caption := IntToStr(i);
MyItem.Width := -2;
Afterwards I use the onData event to populate the ListView like this:
procedure TMatrixDictViewerFrame.StringListViewData(Sender: TObject;
Item: TListItem);
var
ItemCaption: string;
ItemText: string;`
begin
ItemCaption := Format('[%d]', [Item.Index]);
ItemText := FItems[Item.Index];
Item.Caption := ItemCaption;
Item.SubItems.Add(ItemText);
end;
It works fine since in the First column I have the Itemcaptions and in the second column I get the Itemtexts. What I couldnt figure out tho is how to populate the ListView depending on the data I get.
For example: I have a matrix A which is a 3x3 Matrix and I want its elements to be shown in this ListView so the first row shows the first 3 row elements, the second row shows the second row three elements and so on. Questions: how can I access the third column? How can I populate the view according to the Index I have (i,j)?
Best regards
The Index property of the list item tells you the row. You are expected to populate the entire row. Like this:
procedure TMatrixDictViewerFrame.StringListViewData(Sender: TObject; Item: TListItem);
begin
// A is a 3x3 matrix, that you obtain by means we don't know
Item.Caption := FloatToStr(A[Item.Index, 0]);
Item.SubItems.Add(FloatToStr(A[Item.Index, 1]));
Item.SubItems.Add(FloatToStr(A[Item.Index, 2]));
end;

Reorder rows in TClientDataSet

In order to show my data in some order, I have ORDER column. So I can move up and down rows. That is already working, I just swap ORDER number for the affected rows. My problem is, when I delete a row, I need to reorder all other rows. To show the rows in the proper order I use the .IndexFieldNames := 'ORDER', but when I iterate through all rows and assign the new ORDER number, with active index its pretty slow. When I remove the .IndexFieldNames := '', then the rows are not in the correct order anymore, and the new order is wrong.
Here is my code:
class procedure TDataModuleEx.ReOrderColumn(const aDataSet: TClientDataSet;
const aFieldName: string; aResetClone: Boolean);
var
clone: TClientDataSet;
newOrderNo: Integer;
begin
newOrderNo := 0;
clone := TClientDataSet.Create(nil);
try
clone.CloneCursor(aDataSet, aResetClone);
clone.IndexFieldNames := aFieldName;
clone.IndexFieldNames := ''; //Indexed Edit is too slow
clone.First;
while not clone.eof do
begin
Inc(newOrderNo);
clone.Edit;
clone.FieldByName(aFieldName).AsInteger := newOrderNo;
clone.Post;
clone.Next;
end;
finally
clone.Free;
end;
end;
How can I reorder my rows after one is deleted efficiently?
I made some testing on a table with 10 000 records. Procedure ReOrderColumn made renumbering for about 3 seconds. ClientDataSet. ApplyUpdate took about 30 seconds.
Total about 33 seconds.
I wrote stored procedure in Firebird database which made renumbering for about 3 seconds.
SET TERM ^ ;
create or alter procedure REORDER
as
declare variable ID integer;
declare variable I integer;
BEGIN
i = 0;
FOR
select new_table.id
from new_table
order by FORDER
INTO :ID
as cursor tcur
DO
BEGIN
i = :i + 1;
update new_table t set t.forder = :i
where current of tcur;
END
END^
SET TERM ; ^
From Delphi
procedure Reorder;
begin
//Apply changes to server
dm1.ClientDataSet1.ApplyUpdates(0);
//execute stored procedure
dm1.spReorder.ExecProc; <--- ABOUT 3 SEC FOR 10 000 records
//Refresh dataset
dm1.ClientDataSet1.Close;
dm1.ClientDataSet1.Open;
end;

Delete row in StringGrid- Delphi

I want to make something like that. I have a list in my StringGrid and i want to delete one row by selecting cell and then clicking the button. Then this list should show again in StringGrid without this row. The biggest problem i have with deleting row, i tried whis one procedure but it only deleted row in StringGrid, not in list, i think.
procedure DeleteRow(Grid: TStringGrid; ARow: Integer);
var
i: Integer;
begin
for i := ARow to Grid.RowCount - 2 do
Grid.Rows[i].Assign(Grid.Rows[i + 1]);
Grid.RowCount := Grid.RowCount - 1;
end;
Please someone for help. :)
If you're using a standard VCL TStringGrid (without using the live bindings made available in recent versions), you can use an interposer class to access the protected TCustomGrid.DeleteRow method.
The following code has been tested in Delphi 2007. It uses a simple TStringGrid dropped on a form, with the default columns and cells, and a standard TButton.
The TForm.OnCreate event handler simply populates the grid with some data to make it easier to see the deleted row. The button click event deletes row 1 from the stringgrid every time it's clicked.
Note: The code does no error checking to make sure that there are enough rows. This is a demo application and not an example of production code. Your actual code should check the number of rows available before attempting to delete one.
// Interposer class, named to indicate it's use
type
THackGrid=class(TCustomGrid);
// Populates stringgrid with test data for clarity
procedure TForm1.FormCreate(Sender: TObject);
var
i, j: Integer;
begin
for i := 1 to StringGrid1.ColCount - 1 do
StringGrid1.Cells[i, 0] := Format('Col %d', [i]);
for j := 1 to StringGrid1.RowCount - 1 do
begin
StringGrid1.Cells[0, j] := Format('Row #d', [j]);
for i := 1 to StringGrid1.ColCount - 1 do
begin
StringGrid1.Cells[i, j] := Format('C: %d R: %d', [i, j]);
end;
end;
end;
// Deletes row 1 from the stringgrid every time it's clicked
// See note above for info about lack of error checking code.
procedure TForm1.Button1Click(Sender: TObject);
begin
THackGrid(StringGrid1).DeleteRow(1);
end;
If you're using a more recent version, and have attached the data to the grid using live bindings, you can just delete the row from the underlying data and let the live bindings handle removing the row.
The selected row can be retrieved StringGrid1.selected and the you can call the following procedure.
procedure TUtils.DeleteRow(ARowIndex: Integer; AGrid: TStringGrid);
var
i, j: Integer;
begin
with AGrid do
begin
if (ARowIndex = RowCount) then
RowCount := RowCount - 1
else
begin
for i := ARowIndex to RowCount do
for j := 0 to ColumnCount do
Cells[j, i] := Cells[j, i + 1];
RowCount := RowCount - 1;
end;
end;
end;

Progress Bar progression while saving a StringGrid

I am developing a program and I have a StringGrid on it; when a particular button is pressed, my program saves the stringgtid into c:\myfolder\tab9.txt. I would like to put a progress bar that indicate how many time remains at the end of the saving process because sometime the grid has a lot of rows and it could take some time. I am using this code:
procedure SaveSG(StringGrid:TStringGrid; const FileName:TFileName);
var
f: TextFile;
i,k: Integer;
begin
AssignFile(f, FileName);
Rewrite(f);
with StringGrid do
begin
Writeln(f, ColCount); // Write number of Columns
Writeln(f, RowCount); // Write number of Rows
for i := 0 to ColCount - 1 do // loop through cells of the StringGrid
for k := 0 to RowCount - 1 do
Writeln(F, Cells[i, k]);
end;
CloseFile(F);
end;
I call the procedure in this way: SaveSG(StringGrid1,'c:\myfolder\myfile.txt');. My problem is that I don't understand how to do a progress bar that indicate the progress of the saving. At the moment I've only declared ProgressBar1.Position:=0 and ProgressBar1.Max:=FileSize. Do you have any suggestions?
How many cells are we talking about? Your main bottleneck is that you're writing to file for each cell, instead doing buffered writing.
I suggest that you fill TStringList with data from TStringGrid, and use TStringList.SaveToFile() method.
I've tested following procedure on StringGrid with 10,000,000 cells (10,000 rows x 1,000 columns), and it saves data to disk in less than one second:
procedure SaveStringGrid(const AStringGrid: TStringGrid; const AFilename: TFileName);
var
sl : TStringList;
C1, C2: Integer;
begin
sl := TStringList.Create;
try
sl.Add(IntToStr(AStringGrid.ColCount));
sl.Add(IntToStr(AStringGrid.RowCount));
for C1 := 0 to AStringGrid.ColCount - 1 do
for C2 := 0 to AStringGrid.RowCount - 1 do
sl.Add(AStringGrid.Cells[C1, C2]);
sl.SaveToFile(AFilename);
finally
sl.Free;
end;
end;

Resources