I have a Delphi 7 project using Zeoslib 6.6.6 and Sqlite3.
On the form I have a Zquery selecting everything out of a sample database table along with a bunch of calcuated fields (TFloatField; TCurrencyField). The OnCalcFields event of the query runs fine and all field values are set.
However, when i try to loop over the dataset, I consistently get a 'List index out of bounds (62893)' exception, though i am well within the field count limit (the first calculated field of about 14).
Snippet:
gd is a TStringGrid, ZQuery4 is a TZQuery
while not ZQuery4.Eof do
begin
row := row + 1;
gd.Cells[0, row] := IntToStr(gd.Row);
gd.Cells[1, row] := ZQuery4pid.Value; //Known column
gd.Cells[2, row] := FormatFloat('0.00', ZQuery4area.Value); //known column
for i := 3 to ZQuery4.FieldCount - 1 do
begin
field := Zquery4.Fields[i]; //crashes here when accessing the first calculated field.
if field.IsNull
then gd.Cells[i, row] := ''
else gd.Cells[i, row] := field.AsString;
end;
end;
The bizarre thing is that if i connect a DBGrid to the query it works fine. Any ideas?
What happens when gd.cells[x,row] exceeds the number of rows you have set in the string grid? Probably that's your error. If you set your stringgrid to have 62000 rows, great, that's your error. Otherwise, I suspect that you're hitting a limit.
I always used to have my string grid row count grow like this, with logic after row=row+1:
if gd.RowCount<=row then gd.RowCount := row+1;
If however you really are getting this error at 60k+ stringgrid rows, it is possible that instead what is happening is you are hitting a string grid row length limit.
Since you haven't obviously posted ALL your code, it's hard to know what sets the row count in the string grid where, and how it grows.
In the case that you've hit a limit beyond which a StringGrid no longer operates, I suggest you drop StringGrid and use ExgridView or some other virtual gridview that can handle a very very large amount of data.
I seem to have stumbled across at least a workaround:
changing
gd.Cells[i, row] := field.AsString;
to
gd.Cells[i, row] := field.DisplayText;
seems to have solved the problem.
Related
I am using FastCube 2.0 with Delphi.
I have cube and a slice... I need to hide TOTAL for one column (measure). How do I access it in code?
Thank you
Solution:
fcxSliceSLICENAME.MeasuresContainer.Measures[0].UseDifferentAggForTotals := True;
fcxSliceSLICENAME.MeasuresContainer.Measures[0].AgrFuncForTotals := af_Formula;
(0 indicates index of the column whose total you want to hide)
If i have colA, ColB, Colc, ColD and there are 1000 rows in each column in TAdvStringGrid . I would like check the number of double measurements of values in the 1000 rows in colA, ColB, Colc, ColD of TAdvStringGrid.
I am doing some thing like At first reading ColA, ColB, ColC, ColD values into multidimensional array and looping each element in multi dimensional array and comparing with each row element TAdvStringGrid and when found same using OnDrawcell function, I am labelling and displaying the row with a colour.
However it takes a lot of time. Is there a shorter way to do it. As the rows keep on increasing. Thanks for the answer in advance.
Every row is one measurement and one measurement consist of 4 values in ColA,B, C,D.
List : array of array of double;
SetLength (List,AdvStringGrid.RowCount,4);
for i := 0 to AdvStringGrid.RowCount -1 do begin
j:=0;
List[i,j] := strtofloat(AdvStringGrid.Cells[4,i+1]);
List[i,j+1] := strtofloat(AdvStringGrid.Cells[5,i+1]);
List[i,j+2] := strtofloat(AdvStringGrid.Cells[8,i+1]);
List[i,j+3] := strtofloat(AdvStringGrid.Cells[9,i+1]);
end;{for i}
How do i compare each element with neighbour and mark the duplicate??
Am I correct that every row is one meassurement? So one meassurement consist out of 4 values?
First thing is you shouldn't modify the visual StringGrid in in a loop. In the worst case the StringGrid invalidates and draws again after each action.
So it's good to read all data in an multidimention array.
To eliminate doubles i would sort everything and then compare neigbors. This is a pretty common pattern.
Define any order like ColA accending, then ColB accending, ColC accending and ColD accending and implenent a sort algorithm (like quicksort or mergesort).
After everything is sortet you can traverse the array from highes element to 0 and check if two neighbours are the same.
If you want to mark the double values instead of deleting them consider adding a 5th colum for a value when it is a duplicate.
After all the calcilation i would search for any Function like BeginUpdate() and Endupdate() to make sure that the StringGrid will only draw once.
Do all changes to StringGrid between the call of BeginUpdate() and Endupdate()
Update: your code could look something like this:
var
i:integer;
sortedList: array of array of double;
begin
setlength(List, 1000, 5); // use a fifth row for marking doublicates, set this value to 0
// Fill List like you mentioned here
sortedList = YourSortAlgorithm(List); // sort your List here
for i := high(sortedList) downto 0 do
begin
// compare if entries are duplicates
if sortedList[i,1] = sortedList[i-1,1] and sortedList[i,2] = sortedList[i-1,2] and sortedList[i,3] = sortedList[i-1,3] and sortedList[i,4] = sortedList[i-1,4] then
begin
sortedList[i-1,5] = 1; // 1 means duplicate, 0 means not duplicate
end;
end;
AdvStringGrid1.BeginUpdate;
// update your Stringgrid here
AdvStringGrid1.EndUpdate();
end;
Bye the way, instead of using a two dimentionaly array i would recoment to use a array of record.
Saying for example, that your ColA is a height, ColB is a length, ColC is a Temperature and ColD is a age you could define a record like this
type
TMeasurement = record
height: double;
length: double;
temperature: double;
age: double;
isBoolean: boolean;
end;
var
list: array of TMeasurement;
begin
//...
end;
how to make button on delphi 7 to execute addition with all data in dbgrid delphi.
for example
i have database table with 3 coloumns show in dbgrid,
[CODE_NUMBER][ITEMS NAME][STOCK][NEW_STOCK]
001 Rackets 1 5
002 Sports Shoes 2 5
003 Golf Hat 3 5
... etc
How to create button when i click it, then dbgrid start addition
[STOCK] = [STOCK]+[NEW_STOCK]
after count in the first line, move to second line do the same addition
and so on until the end of the record in dbgrid and delete data in [NEW_STOCK] coloumn.
i've try with
if dbgrid1.fieldbyname('Code').value <> 0 then
begin
dbgrid1.fieldbyname('Stock').value := dbgrid1.fieldbyname('Stock').value + dbgrid1.fieldbyname('NEW_STOCK').value;
dbgrid1.next;
but only affect in the first line, nothing happen with the next lines in dbgrid
To change data in dbgrid, you should use it's corresponding dataset, i.e.:
with dbgrid1.DataSource.DataSet do begin
Edit;
Fields.fieldbyname('Stock').value := Fields.fieldbyname('Stock').value + Fields.fieldbyname('NEW_STOCK').value;
Post;
Next;
end;
More efficient would be to do this at the database
The update statement to send to the database is very simple :
update yourtable set Stock = Stock + newStockValue where Code <> 0
and then just refresh your query or table component.
I'm used to calculate the balance of the calculated field., But when I connect the dbgrid. Calculated by moving the scroll is wrong.
Please get help
var
Form1: TForm1;
i : Integer;
procedure TForm1.FormShow(Sender: TObject);
begin
i := 0;
DataSource1.DataSet := ADOTable1;
DBGrid1.DataSource := DataSource1;
end;
procedure TForm1.ADOTable1CalcFields(DataSet: TDataSet);
begin
i := (ADOTable1Debtor.AsInteger - ADOTable1creditor.AsInteger) + i;
ADOTable1Total.AsInteger := i;
end;
Now run the application and move the scroll in dbgrid column numbers (total) will change.
I'd like to know how to stop the change.
The calculated fields is designed to show value calculations at the row level and is not designed to make aggregations (calculations based on a set of rows).
For example, the database layer will fire the OnCalc event in no particular order and every time is needed to obtain the value of the field (for display pruposes, for example), since that value is not stored and may (and usually do) depend on the values of other fields.
In a set of 10 rows, you can get it called for rows 1, 2, 3, 4, 5 and then in 1 again...
You can use it, for example, in a line_total column which is the product of quantity and unit_price, but you can't use it to show e.g. the sum(line_total) of all the lines, as I infer you're trying to do in the shown code.
How to perform aggregations then?
You may want to link your DataSet to a ClientDataSet, which have AggregateFields, in which you can perform calculations like SUM(Quantity * Price) on the entire row-set or sub-sets based on a Index.
To learn more about AggregateFields read ClientDataSet Aggregates and GroupState by Cary Jensen in EDN.
What you are trying can't work, since any scroll will change the result.
Take a AdoDataset instead with following Commandtext
select ID, creditor, Debtor,(Select sum (Debtor-Creditor) from Table1 t where t.ID<=Table1.ID) as Total
from Table1 order by ID
In Excel's COM API:
Given an ExcelRange object, how would I determine which rows and columns are contained within it?
I do not want the contents of the range, just the "coordinates" of the range, preferably as integers.
I did notice that ExcelRange has both a Row and a Column property, however these only indicate the row and column of the upper left corner of the range.
Note: I am using Delphi, however this question could be relevant to any language using Excel though COM, so answering using Delphi is not necessary.
Assuming a simple rectangular range then you use the Rows and Columns properties of the ExcelRange object. The top-left of the selection is determined by Range.Row and Range.Column. The number of selected rows and columns is given by Range.Rows.Count and Range.Columns.Count.
In complete generality an Excel range can be made up of multiple non-contiguous areas. In this case you use the Areas property of ExcelRange to iterate through the simple rectangular ranges that make up the complex range.
To illustrate consider the following code:
procedure DescribeExcelRange(const Range: ExcelRange);
var
AreaIndex: Integer;
Area: ExcelRange;
begin
for AreaIndex := 1 to Range.Areas.Count do
begin
Area := Range.Areas[i];
Writeln(Format(
'Area %d: R%dC%d:R%dC%d',
[AreaIndex, Area.Row, Area.Column,
Area.Row+Area.Rows.Count-1, Area.Column+Area.Columns.Count-1]
));
end;
end;
I have not actually tested this code so I hope I have remembered correctly that indexing is 1-based.