List index out of bounds whilst deleting items - delphi

I solved my problem but I need to know why this problem raised to me ?!
I write a project that load file to listBox then delete the strings one by one,
but when I delete listBox strings this exception raised to me!
list index out of bounds (5) !
I type this for loop to read list box and delete strings:
for i := 0 to ListBox3.Count -1 do
begin
ShowMessage(ListBox3.Items[i]);
ListBox3.items.Delete(i);
end;
and my problem solved by do a little change in for-loop statement
for i := ListBox3.Items.Count - 1 downto 0 do
begin
ShowMessage(ListBox3.Items[i]);
ListBox3.items.Delete(i);
end;
Why the first statement raised an exception, and the second one work fine ?

By deleting items moving forward, you're cutting the branch off that you're standing on. :-) The upper bounds of the loop is only evaluated once, before the loop begins, and if you delete items there are now fewer in the list than there were when the bound was calculated.
Loop limit is evaluated (for example, List.Count - 1 = 5). Valid indexes into it are [0..4]
The loop starts, and you retrieve List[0] and delete it. List Count = 4,
bounds is still 5
The index is incremented, you retrieve and delete List[1]. List Count = 3, bounds is still 5
The index is incremented, you retrieve and delete List[2]. List Count = 2, bounds is still 5.
The index is incremented, you retrieve List[3] - Oops! There are only 2 items in the list, now at indexes [0..1] - List index out of bounds(3).
By iterating backwards, even though the bounds is still only calculated at the beginning, you're removing the items from the end and decrementing the count at the same time.
Bounds is 5, and you retrieve List[4] and delete it. Count is now 4, bounds is still 5
Index is decremented, and you retrieve List[3] and delete it. Count is now 3, bounds is still 5
Index is decremented, and you retrieve List[2] and delete it. Count is now 2, bounds is still 5.
Index is decremented, and you retrieve and delete List[1]. Count is now 1, bounds is still 5.
Index is decremented, and you retrieve and delete List[0]. List is now empty, but we've reached the terminating condition of the loop (downto 0) and the loop exits safely.

Each time you delete an item from the list, the list contains one less item. However, the for statement copies the list count at the beginning, and is not updated upon each iteration. Therefore, by the time you get halfway through the list, the counter i becomes larger than the current (new) list count, even though the list no longer contains the original number of items.
As an alternative, you could also do a loop like this:
while ListBox3.Items.Count > 0 do begin
ShowMessage(ListBox3.Items[0]);
ListBox3.items.Delete(0);
end;

There may be situation when you DO NOT delete certain items. Then the generic approach would be
i := 0;
while i < ListBox3.Items.Count do
begin
ShowMessage(ListBox3.Items[i]);
if <wantToDelete> then // some condition there
ListBox3.Items.Delete(i)
else
Inc(i);
end;

Related

How to delete all items of a TFlowLayout at runtime?

I want to clear a FlowLayout at run time, is there a function to do that ?
I thought about mapping all its items and free theme, but I don't know how to access its items, any code example please ?
Using the Children and ChildrenCount properties you can free the items, either in reverse order
for i := FlowLayout1.ChildrenCount-1 downto 0 do
FlowLayout1.Children[i].Free;
or in forward order (repeatedly addressing item with index 0)
for i := 0 to FlowLayout1.ChildrenCount-1 do
FlowLayout1.Children[0].Free;

Deleting a entry in a DBGrid with a button in Delphi

I'm relatively new to delphi and I would like to know how to delete an entry in a dbGrid without using a dbNavigator but a button. The number of the entry that should be deleted must be entered in a spinedit, not be clicked on in the dbGrid. Thanks for any help.
First it's nice to position in the first record of the DataSet, then it will delete from the first to the N record.
DBGrid1.DataSource.DataSet.First;
Now you create the Loop (Don't forget to create the variable {var I : integer})
For I:=0 to SpinEdit1.Value-1 Do
Before start deleting records, you will need to verify if there is any record on DataSet.
You can do something like this:
if DBGrid1.DataSource.DataSet.RecordCount > 0 then
And finally you can delete the record
DBGrid1.DataSource.DataSet.Delete;
The final code would be like this:
DBGrid1.DataSource.DataSet.First; //Set on the first Record of the DataSet
For I:=0 to SpinEdit1.Value-1 Do //Do loop
if DBGrid1.DataSource.DataSet.RecordCount > 0 then //Check if have records
DBGrid1.DataSource.DataSet.Delete; //Delete

Why AddGantt or AddGanttColor returns the same index for different invocations?

For some reason for certain cases when adding gantts to a TGanttSeries instance using method AddGanttColor or AddGantt the same index is returned for two different calls.
For example, the following snippet illustrates the case where the last two calls both return index of value 1.
Series.AddGanttColor(0, 100, 0, 'WA #234', clGreen); // returns value 0
Series.AddGanttColor(100, 200, 1, 'WA #235', clGreen); // returns value 1
Series.AddGanttColor(50, 200, 2, 'WA #237', clGreen); // returns value 1!
However, all three gantts are displayed. So, it seems that the third line added gantt as the second in the list, and moved the gantt added on the second line to be the last one (third, and having index 2).
Could anyone please explain how could this be remedied to ensure that returned indexes stay associated with gantts as per AddGanttColor calls. In the above example, I would expect index values 0, 1, and 2 to be associated with the first, second and third added gantt respectively.
Have just confirmed that this situation holds true for TeeChart versions 5 and 8 running under Delphi 5.
Well... after some research the cause of the problem was narrowed down to the value of property Series.XValues.Order. This property should be set to loNone in order to preserve association between index values returned from AddGantt and corresponding gantts.
IMO this is an API deficiency.

How to delete certain rows from Access via Delphi?

while not TBLOrder.Eof do
begin
TBLOrder.Locate('OrderID', Total, []);
TBLOrder.Delete;
end;
This just deletes every single row in my Access Database, which is really annoying.
I'm trying to get the program to delete the selected row (which is Total).
From what I understand, It should locate the selected row, which is equal to Total. e.g. If Total = 3 it should find the row where OrderID = 3 and then delete that row.
Any help is appreciated.
Try this instead (Max's routine requires you to loop through the entire dataset, which is fine unless it's got many rows in it):
while (TblOrder.Locate('OrderID', Total, [])) do
TblOrder.Delete;
TDataSet.Locate returns a Boolean; if it's True, the found record is made the active record and you can then delete it. If it returns False (meaning the record is not found), the call to Delete is never made.
BTW, the problem with your original code is that you test for Eof, but never check to see if the Locate finds the record; you just delete whatever record you're on, and then test for Eof again. If you're not at Eof, you call Locate, ignore whether or not it found the record, and delete whatever row you're on. This then repeats over and over again until there are no more records, at which point Eof returns true and you break the loop.
If there is just one row that contains an ORDERID equal to 3, you don't need the WHILE loop.
If you expect more than one row with an ORDERID equal to 3, do this:
TBLOrder.first; // you could also do the locate here if it's a big table
while not TBLOrder.Eof do
begin
if TBLOrder.FieldByName('OrderID').AsInteger = 3 then
TBLOrder.delete
else
TBLOrder.next;
end;
Otherwise, you could also use SQL.

Paradox SetRange does not provide correct result when querying 3 fields

I have a problem with setting a range on a secondary index in a Paradox 7 table using Delphi2010.
The relevant fields are:
FeatureType (int); YMax (int); XMax (int); YMin (int); Xmin (int).
The secondary index contains all these fields in this order.
I tested using a SetRange statement like so (not necessary to add all field values, rest is assumed NULL and all values are included):
table1.IndexName := 'YMaxIndex';
table1.SetRange([101, 280110400],[101, 285103294]); //386236 records
And tried to get a 0 result by adding to the constraints:
table1.IndexName := 'YMaxIndex';
table1.SetRange([101, 280110400, 1],[101, 285103294, 1]); //386236 records
But still gets 3863236, which is clearly incorrect when checking the values in the XMax field in the table.
Can someone please explain to me what I am not understanding about Paradox index and SetRange? I have used similar code frequently but not necessarily with 3 fields specifying the range.
Update
See Uwe's response below. The final code solution follows (new ranges for XMax):
Table1.SetRange([101,280110400], [101,285103294]);
Table1.Filter := 'XMax > 100000 and XMax < 110000';
Table1.Filtered := true;
An index range is always taken as a whole over all fields and not looking for each field individually. The result set will contain every record that is in between those ranges. The comparison is made for each index field in the given order.
In your case it will check if the record's FeatureType lies in between 101..101. If the field contains 101 it is taken into consideration. As the field value lies at the border of the range, the next fields are checked.
If the YMax field lies in between 280110400..285103294 and the value doesn't match the borders (280110400 or 285103294), it is taken into the result set without any further checking. In that case the remaining index fields are not checked.
The result you are trying to get is only possible with a filter condition - or with an appropriate SQL Select clause.
for range set with
table1.SetRange([101, 280110400, 1],[101, 285103294, 1]);
Folow values are in range
101 280110400 1
101 280110400 2
101 280110400 3
....
101 280110401 -maxint
....
101 280110401 maxint
....
101 285103294 0
101 285103294 1
A little clarification to the previous answers:
SetRange checks separately the range start and end conditions, for example we have
SetRange([1,2], [2,2])
and record (1, 3);
Range start: we have 1 = 1 for the first field (boundary), so we check the second field (2 < 3) - the range start condition is satisfied.
Range end: we have 1 < 2 for the first field (not boundary), so the second field is not checked - the range end condition is satisfied.
The record is in range.

Resources