Subject Re: [IBO] TIB_Grid recalc before changing selected Row
Author Geoff Worboys
> I have a TIB_Grid wherein I'd like to recalculate a Row after the
> user changes a value in that row. I connected my recalc routine to
> the OnCellLoseFocus event which works fine if the user leaves the
> cell and then moves to a cell in the same row, but if they move up
> or down (to a different row) then the Selected row has changed and
> the recalc routine is working off the wrong row.

Two things...

1. When wanting to respond to row changes it is best to work with
dataset/datasource events - rather than controls.

2. Working with grids can often be very timing sensitive - as you have
discovered.


I actually have an app written by Hie Joen that we worked on trying to
come up with the best solution for recalcuating rows dynamically.
I've been meaning to get around to adding comments etc to make it
worth publishing as a techsheet or something. In that sample there
were actually two issues involved, below I've copied the resulting
solutions that we came up with.

--
Geoff Worboys
Telesis Computing



* LookupCombo and assigning other fields from the lookup dataset:

If it is just a matter of wanting to update alternate display-only
fields (calculated or computed fields in the main dataset) then it is
a simple matter of adding entries to the KeyDescLinks.

The example below required that the price of a product selected
from a lookupcombo should assigned to the price field (real not
computed or calculated) of the main dataset as the default price of
the order item. From there the user could override the price if
required.

Originally tried using LookupCombo.OnCloseUp - but that does
not work well when the lookupcombo is on IB_Grid (the resulting
dataset interactions cause search values to be reset), and besides
which this requires that the lookup has AutoDropDown set true to catch
all selections. We also tried LookupDataSource.OnDataChange but this
had other issues that could not be easily worked around.

The solution which finally worked reliably was using the AfterScroll
event of the lookup dataset...

procedure TForm1.LookupQueryAfterScroll(IB_Dataset: TIB_Dataset);
begin
if MainQuery.State in [dssEdit,dssInsert] then
MainQuery.FieldByName('PRICE').AsCurrency :=
LookupQuery.FieldByName('DEFAULT_PRICE').AsCurrency;
end;

This successfully assigned DEFAULT_PRICE from the lookup dataset to
the PRICE on the main dataset, without causing interaction problems
with the lookupcombo or the various other field calculations that had
to take place.


* OnDataChange event code:

The example code below is attempting to force a recalculation of the
calculated fields when a particular field in the dataset changes. (For
example, perhaps you want a "total price" field to be updated when the
price or quantity fields are changed.)

The following does work *most* of the time and does not experience
recursion problems. However I do not recommend such code because it
assumes that Field is provided when needed during dssEdit/dssInsert
states (and this is not true for foreign key fields used in lookup
links and it may not be true in some other instances). Note also that
this code assumes that Field is ONLY provided during dssEdit/dssInsert
states and that is not documented/guaranteed either (even though I
think it is true at this time).

procedure TForm1.IB_Query1DataChange(Sender:TIB_StatementLink;
Statement: TIB_Statement; Field: TIB_Column);
begin
if Assigned(Field) and (Field.FieldName = 'MYFIELD') then
IB_Query1.CalculateFields;
end;

The following code is much more reliable and does not rely on
undocumented features. FInternalChange is an integer that must be
declared to the form interface - to prevent recursion. Without this
the CalculatedFields call results in a further DataChange event and so
you end up with infinite recursion.

procedure TForm1.IB_Query1DataChange(Sender:TIB_StatementLink;
Statement: TIB_Statement; Field: TIB_Column);
begin
if FInternalChange = 0 then
try
Inc(FInternalChange);
if IB_Query1.State in [dssEdit,dssInsert] and (
(Assigned(Field) and (Field.FieldName = 'MYFIELD')) or
(Field = nil) ) then
IB_Query1.CalculateFields;
finally
Dec(FInternalChange);
end;
end;