Progress Bar progression while saving a StringGrid - delphi

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;

Related

Insert column into string grid, delphi

I have a string grid, from which i can delete columns. I defined a CustomStringGrid type that allows me to use DeleteColumn method.
This is how it looks:
TCustomStringGrid = class(TStringGrid)
[...]
With tCustomStringGrid(mygrid) do
DeleteColumn(col)
end;
IS there something similar to add a column? I've tried InsertColumn but it doesn't seem to exist. I want to add a column at a particular position. In fact, if a user deletes a column i have an undo button which i want to reinsert the deleted column (i'm keeping the data in an array so i can recreate the column but i don't know how to insert one in a particular position).
Thank you!
It's not built in but easy to emulate, with ColCount = ColCount + 1 and MoveColumn from a HackClass.
type
THackGrid=Class(Grids.TCustomGrid)
End;
Procedure InsertColumn(G:TStringGrid;Position:Integer);
begin
if Position<G.ColCount then
begin
G.ColCount := G.ColCount + 1;
THackGrid(g).MoveColumn(G.ColCount - 1,Position);
end;
end;
procedure TMyForm.Button1Click(Sender: TObject);
begin
InsertColumn(StringGrid1,1);
end;
THack grid is not working, maybe it is ok when both cols are visible, but that works always :
Procedure MoveColumn(G:TStringGrid;OldPosition : integer;NewPosition:Integer);
var
i : integer;
temp : string;
begin
for i := 0 to g.rowcount - 1 do
begin
temp := g.cells[OldPosition,i];
g.cells[OldPosition,i] := g.cells[NewPosition,i];
g.cells[NewPosition,i] := temp;
end;
end;

Variable in for loop is not getting called

I am using DBXJson to parse a simple json file called response.jsonand show it's contents in a grid, but only the first row of the grid ever gets populated with the data and even though there is more rows/data to display. I am using a custom grid in the code below but I have tried a variation of the below code using the standard stringgrid and it exhibited the same behavior. This is the code I am using to parse the response and show it in my grid.
var
sl: TStringList;
LJsonArr: TJSONArray;
LJsonValue: TJSONValue;
LItem: TJSONValue;
col, row: Integer;
begin
col := 0;
row := 0;
sl := TStringList.Create;
sl.LoadFromFile('response.txt');
LJsonArr := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(sl.text), 0)
as TJSONArray;
for LJsonValue in LJsonArr do
begin
NextGrid1.AddRow();
for LItem in TJSONArray(LJsonValue) do
begin
NextGrid1.Cells[col, row] := TJSONPair(LItem).JsonValue.Value;
inc(col);
end;
inc(row);
end;
sl.Free;
end;
I suspect that the problem lies in the fact that the row variable is out of place and is not getting called and that is causing only the first row to display, but I could be mistaken and I am hoping that a fresh pair of eyes can spot the problem.
The problem is that col must be re-initialised to zero every time you start a new row. So move the initialization of col into the outer loop.
row := 0;
for LJsonValue in LJsonArr do
begin
col := 0;
NextGrid1.AddRow();
for LItem in TJSONArray(LJsonValue) do
begin
NextGrid1.Cells[col,row] := TJSONPair(LItem).JsonValue.Value;
inc(col);
end;
inc(row);
end;
I don't know this JSON library but if it allows you to access array elements with random access then a traditional oindexed for loop would lead to cleaner code that the for in loop that you use. In pseudo code:
for row := 0 to arr.length do
begin
item := arr[row];
for col := 0 to item.length do
grid.Cells[col,row] := item[col];
end;
As a rule of thumb, for in loops are better if you do not need to know the item index. However, as soon as you need to know the item index then traditional indexed for loops are usually to be preferred.

TStringGrid Labeling outside column

I am having some issues with a piece of code I wrote. I am using a TStringGrid to draw a seating plan.
What it is supposed to do is label the fixedcol and fixedrow with the letter down the column and numbers for the rows.
My problem is i don't know how to change my code so that it excludes the cell [0,0]. It is also not labeling all the rows.
procedure TfrmDraw.FormCreate(Sender: TObject);
var
i, j, k: Integer;
begin
sgFloor.RowCount := adotSeats['Rows'] + 1;
sgFloor.ColCount := adotSeats['Seats_per_Row'] + 1;
for i := 0 to SgFloor.RowCount do
begin
for j := 0 to SgFloor.ColCount do
begin
if i = 0 then
SgFloor.Cells[i,j] := Chr(65 + j)
else
if j = 0 then
begin
for k := 1 to sgFloor.ColCount do
SgFloor.Cells[i,0] := IntToStr(i) ;
end;
end;
end;
end;
Screenshot:
Thanks
Some good advice:
I know how easy it is to use the RAD style components,
but try not to bind GUI logic and application logic.
This will make your code cleaner and easier to read and maintain.
Also use meaningful names for your variables, doing this will prevent stupid mistakes.
Now about your problem,
the Grid uses 0-based indexes so the last Index is one less as the count.
The fixed row and column both have Index 0 in your case which means we have to start iterating from the next index, which is 1, I used the FixedRows and FixedCols properties to make it more readable. This has the added bonus that it will label the most inner fixed Rows/Cols if you have more than one Fixed row/column. It is easier to make 2 separate loops, one for the header row and one for the columns :
procedure SetupGrid(Grid : TStringGrid; Rows, Columns : Integer);
var
Row, Col: Integer;
begin
Grid.FixedCols := 1;
Grid.FixedRows := 1;
Grid.RowCount := Rows + Grid.FixedRows;
Grid.ColCount := Columns + Grid.FixedCols;
for Row := Grid.FixedRows to Grid.RowCount-1 do
Grid.Cells[0, Row] := Chr(Ord('A') + Row-1);
for Col := Grid.FixedCols to Grid.ColCount-1 do
Grid.Cells[Col, 0] := IntToStr(Col);
end;
procedure TfrmDraw.FormCreate(Sender: TObject);
begin
// try to make your GUI events as lightweight as possible and seal
// your code into separate functions/classes, this will improve readability
// of the GUI units and it will make your code testable
SetupGrid(sgFloor, adotSeats['Rows'], adotSeats['Seats_per_Row']);
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;

ListView Column Resize in Delphi XE4

I face a issue with dynamically resizing the column width of a TJVListview in Delphi XE4 (in Windows 7 environment). Application takes longer time for column resize and sometimes throws access violation if there are huge data on the listview. We are using the below code for resizing the columns.
for i := 0 to LV.Columns.Count -1 do
begin
if LV.Columns.Items[i].Tag = 0 then
begin
LV.Columns.Items[i].Width := ColumnTextWidth;
LV.Columns.Items[i].Width := ColumnHeaderWidth;
end;
end;
Previously the same code used to work fine with Delphi 2009. The problem I noticed only when we are using customdrawitem event(Where we are placing images inside the listview). For the normal listview with only text display the above code is working fine.
I tried using the Column AutoSize property by setting it true, but it is of no use.
Any suggestion on how to overcome this issue. Actually, we are using the TJVlistview component in number of places in our application.
Regards,
Siran.
cODE :
1) In my form I have a JVListview, Button and imagelist. Button for loading into List view.
2) in Advancecustomdrawitem, I try to place a BMP control and also perform alternative row color change...
procedure TForm1.Button1Click(Sender: TObject);
var
i, ii: Integer;
ListItem: TListItem;
strVal : String;
begin
strVal := 'Test String';
try
ListView.Items.BeginUpdate;
LockWindowUpdate(listview.handle);
try
ListView.Clear;
for i := 1 to 15 do
begin
ListItem := ListView.Items.Add;
ListItem.SubItems.Add(strVal +'_' +IntToStr(i));
ListItem.SubItems.Add(strVal +'_' +IntToStr(i));
ListItem.SubItems.Add(strVal +'_' +IntToStr(i));
ListItem.SubItems.Add(strVal +'_' +IntToStr(i));
ListItem.SubItems.Add(strVal +'_' +IntToStr(i));
end;
finally
// for resizing the columns based on the text size
FitToTextWidth(ListView);
ListView.Items.EndUpdate;
LockWindowUpdate(0);
end;
except
on E: Exception do
MessageDlg(PWideChar(E.Message), TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0);
end;
end;
procedure TForm1.FitToTextWidth(LV: TListView);
var
i : integer;
begin
// Set the Column width based on based on textwidth and headerwidth
for i := 0 to LV.Columns.Count -1 do
begin
if LV.Columns.Items[i].Tag = 0 then
begin
LV.Columns.Items[i].Width := ColumnTextWidth;
LV.Columns.Items[i].Width := ColumnHeaderWidth;
end;
end;
end;
procedure TForm1.LISTVIEWAdvancedCustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
var DefaultDraw: Boolean);
Var
R : TRect;
C : TCanvas;
B : TBitMap;
begin
// Set C
C := (Sender as TListView).Canvas;
// Set R
R := Item.DisplayRect(drLabel);
B := TBitMap.Create;
B.Transparent := True;
B.TransparentColor := clWhite;
// based on item index set the image and change the row color
if odd(item.Index) = true then
begin
ImageList.GetBitmap(0,B);
TJvListItem( Item ).Brush.Color := clWhite;
TJvListItem( Item ).Font.Color := clBlack;
end
else
begin
ImageList.GetBitmap(1,B);
TJvListItem( Item ).Brush.Color := clMoneyGreen;
TJvListItem( Item ).Font.Color := clBlack;
end;
C.Draw(R.Left + 5 ,R.Top, B);
B.Free;
end;
The above code works well with Delphi 2009... but currently trying migrating to XE4 in Win 7 environment.. my problem here is, it takes lot of time in loading the list view (When performing column resizing dynamically by calling FitToTextWidth method) .. but without this method it is working fine but without column resizing...
When you set the width of a column to any one of the automatic constants, the control have to evaluate the length of the items/subitems to be able to calculate the necessary width. This takes time.
Also, when you set the width of a column, the VCL ListView updates all columns.
You have six columns, setting the width of any one of them involves 6 column updates, together with the spurious call in your FitToTextWidth procedure, your code is causing reading all items/subitems of a column 42 times (due to the code path in VCL: 1 time for 1st col, 2 times for 2nd -> 21 times for setting the width of 6 columns). Enclose your width setting in Begin/EndUpdate calls and remove the extra call, and you'll finish it in 6 rounds.
procedure TForm1.FitToTextWidth(LV: TListView);
var
i : integer;
begin
// Set the Column width based on based on textwidth and headerwidth
LV.Columns.BeginUpdate;
try
for i := 0 to LV.Columns.Count -1 do
begin
if LV.Columns.Items[i].Tag = 0 then
begin
// LV.Columns.Items[i].Width := ColumnTextWidth;
LV.Columns.Items[i].Width := ColumnHeaderWidth;
end;
end;
finally
LV.Columns.EndUpdate;
end;
end;
As I don't get any AV with your test case, I cannot comment on that.

Resources