Variable in for loop is not getting called - delphi

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.

Related

Removing duplicate lines from TStringList without sorting in Delphi

I know how to remove duplicate strings from a TStringList using dupignore for a sorted Tstringlist.
CallData := TStringList.Create;
CallData.Sorted := True;
Call.Duplicates := dupIgnore;
But in my case strings must not be sorted .
Using a FOR loop finding duplicates is very slow (also using indexOF())when TStringList has hundreds of thousands of lines .
if OpenDialog1.Execute then
begin
Try
y := TStringList.create;
f := TStreamReader.create(OpenDialog1.FileName, TEncoding.UTF8, True);
while not f.EndOfStream do
begin
l := f.ReadLine;
X.Add(l);
end;
g := Tstreamwriter.create('d:\logX.txt', True, TEncoding.UTF8);
for I := 0 to X.count - 1 do
begin
if y.IndexOf(X[I]) = -1 then
y.Add(X[I]);
end;
for j := 0 to y.count - 1 do
g.WriteLine(y[j]);
Finally
f.free;
y.free;
g.free;
End;
end;
is there any better way ?
Here's how I would approach this problem:
Create a dictionary keyed on a string. It doesn't matter that the value type is.
Iterate through the string list in reverse order.
For each string, check whether or not it is in the dictionary.
If it is in the dictionary, remove from the string list. Otherwise add to the dictionary.
If there are a large number of duplicates to be removed then the performance of the above will be affected by repeated removal from the string list. That's because each item to be removed results in the later items being shifted down one index. You can avoid this by copying into a new list rather than deleting inplace.
Alternatively, you can operate in place like this:
Create a dictionary keyed on a string. It doesn't matter that the value type is.
Initialise a variable named Count to zero.
Iterate through the string list in forward order.
For each string, check whether or not it is in the dictionary.
If it is in the dictionary, do nothing. Otherwise add to the dictionary, copy into index Count of the list, and then increment Count.
Once the iteration is complete, resize the list to have Count elements.
The point of the dictionary is that lookup is an O(1) operation and so the second algorithm has O(n) time complexity.
I would use trickery, by having a sorted and an unsorted list. Like this:
y := TStringList.create;
s := TStringList.create;
s.Sorted := TRUE;
s.Duplicates := dupIgnore;
f := TStreamReader.create(OpenDialog1.FileName, TEncoding.UTF8, True);
while not f.EndOfStream do
begin
l := f.ReadLine;
s.Add(l);
if s.Count > y.Count then y.Add(l);
end;
// etc.
function compareobjects
(list : Tstringlist;
index1 : integer;
index2 : integer
) : integer;
begin
if index1 = index2 then
result := 0
else
if integer(list.objects[index1]) < integer(list.objects[index2]) then
result := -1
else
result := 1;
end;
begin
Try
y := TStringList.create;
y.Sorted := true;
y.Duplicates := dupignore;
f := TStreamReader.create('c:\106x\q47780823.bat');
i := 0;
while not f.EndOfStream do
begin
inc(i);
line := f.readline;
y.Addobject(line,tobject(i));
end;
y.Sorted := false;
y.CustomSort(compareobjects);
for i := 0 to y.count - 1 do
WriteLn(y[i]);
Finally
f.free;
y.free;
End;
readln;
end.
I'd keep track of the line number (i) and assign it with the string by casting as an object; sort the list and remove duplicates as before, but then un-sort it using a custom sort on the objects.

How to delete a listbox item (text) from a listview in delphi?

I have a listview which displays text from a loaded dataset.
I need to remove unwanted words that I listed in a listbox.
How to do that in delphi? I tried to convert the items to a text in the listview but the code didn't work for me..
Here's what I wrote:
var
counter,k : Integer; //counters
begin
counter := 0;
k:=0;
while counter <= listview1.Items.Count do
for k := 0 to Listbox1.items.Count-1 do
if listview1.Items.item[counter].Caption=listbox1.items[k] then
begin
listview1.Items.item[counter].Delete;
inc(counter)
end;
end;
There's multiple things wrong with the code:
You're only incrementing counter when you find a match, hence the
loop will not terminate if you don't.
You're using <= in the head of your while-loop, that will lead to
an Access Violation in the last iteration, since you access
the (n+1)-th element in the ListView with n elements.
If you modify the ListView while iterating over it, you have to
iterate from the back to the front. Suppose you find a match for the
first item of the ListView, you will delete it, and
ListView1.Items[counter] will be the item that was at index counter+1
previously. You can avoid that by changing the order of the iteration
(since deleting an element will not influence the following iterations), and breaking if you find a match.
Also, non-critical, but a question of coding style:
You don't have to initialize loop variables for a for-loop (and the
compiler should have hinted that the value assigned to k in line 2 is
never used, which you shouldn't ignore)
If you have a known number of iterations to perform, as you do for
the outer loop, you usually want to use a for-loop.
Your accessing of the items looks a little weird, though it probably
works.
TL;DR, here's how I would write the code:
procedure TForm1.Button2Click(Sender: TObject);
var counter,k: integer;
begin
for counter := ListView1.Items.Count-1 downto 0 do
for k := 0 to ListBox1.items.Count-1 do
if ListView1.Items[counter].Caption = ListBox1.Items[k] then
begin
ListView1.Items.Delete(counter);
Break;
end;
end;
You increment the outer counter counter in the wrong place. It is easier to code the counter to count backwards when you delete items that are indexed by the counter. Try this:
var
counter,k : Integer; //counters
begin
// counter := 0;
// k:=0;
for counter := listview1.Items.Count-1 downto 0 do
begin
for k := 0 to Listbox1.items.Count-1 do
if listview1.Items.item[counter].Caption=listbox1.items[k] then
begin
listview1.Items.item[counter].Delete;
Break;
end;
end;
end;

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;

Creating TQReport elements at run time

Creating TQReport elements at run time.
Well, at least trying...
I don't know what headings or data shall appear on this report. I get a TList of TStrings representing the data rows and columns. I plant the 'Create' directives in the band print event for the group and the OnNeedData event for main data row bands.
But nothing appears. Must I make the labels at design time? Do not want.
To get you started, this works:
// uses QuickRpt, qrpBaseCtrls, QRCtrls, QRPrntr;
procedure TForm1.Button1Click(Sender: TObject);
var QR: TQuickRep;
QB: TQRBand;
QL: TQRLabel;
begin
QR := TQuickRep.Create(Self);
try
QR.PrintIfEmpty := True;
QB := TQRBand.Create(Self);
QB.Parent := QR;
QB.BandType := rbTitle;
QL := TQRLabel.Create(Self);
QL.Parent := QB;
QL.Left := 10;
QL.Top := 10;
QL.AutoSize := True;
QL.Caption := 'This works';
QR.Preview;
finally QR.Free;
end;
end;

Resources