10

I have a ClientDatSet with a few fkInternalCalc fields. The CDS is not linked to any provider; instead it's filled on the fly. How can I force CDS to recalculate all the "calculable" fields? I can not call Refresh() because there is no provider to refresh data from. The only way I have come with so far has been to navigate through all records, which is not the best way.

PS: I have read this question and this post, but I'm hoping for a more elegant way.

Community
  • 1
  • 1
iMan Biglari
  • 4,674
  • 1
  • 38
  • 83
  • So, closing - opening the data set doesn't work? – Sertac Akyuz Jul 28 '13 at 12:22
  • @SertacAkyuz I haven't tried it yet. Doesn't closing a client dataset clear all the data in it? – iMan Biglari Jul 28 '13 at 12:24
  • @SertacAkyuz I was afraid so... What does `Resync()` do? Any ideas? – iMan Biglari Jul 28 '13 at 12:33
  • @Iman: Would you please explain why you need to force the fields calculation? It happens automaticaly everytime a field changes in response to an internal dataset event named **deFieldChanged**. There is a protected method named **TDataset.CalculateFields** that fires the process to the current record, but it is not intented to be used by any code other than the TDataset's (or a subclass of it). All this because, theoretically, a programmer wouldn't need to fire the calculation process him/herself. – AlexSC Jul 28 '13 at 13:53
  • 1
    @AlexSC, that won't work for fkInternalCalc fields. – Uwe Raabe Jul 28 '13 at 13:56
  • @iMan - It fetches data from the database if I'm not mistaken. The only method I was able to figure out which does not fetch data, is the one I had mentioned in a comment to the answer of the question that you linked. You've most probably already seen it. – Sertac Akyuz Jul 28 '13 at 14:11
  • @Uwe: the code for calculating InternalCalc fields (**RefreshInternalCalcField**) is called by the same method that calls **CalculateFields**. It's an inner procedure declared in the scope of **DataEvent**. This is all true in Deelhi XE3, at least. So, for that particular version of Dephi, I believe it applies for both Calculated fields and InternalCalc fields. – AlexSC Jul 28 '13 at 14:27
  • @AlexSC I need to force fields calculation because the calculated fields need to reflect changes in external events. – iMan Biglari Jul 28 '13 at 18:05

2 Answers2

8

I achieve that with a helper (stripped here to the necessary), which allows to call the protected methods without any hack. Make sure to check for DataSet.State = dsInternalCalc inside OnCalcFields for fkInternalCalc fields.

type
  TClientDataSetHelper = class helper for TClientDataSet
  public
    function AssureEditing: Boolean;
    procedure InternalCalc;
  end;

function TClientDataSetHelper.AssureEditing: Boolean;
begin
  result := not (State in [dsEdit, dsInsert]);
  if result then
    Edit;
end;

procedure TClientDataSetHelper.InternalCalc;
var
  needsPost: Boolean;
  saveState: TDataSetState;
begin
  needsPost := AssureEditing;
  saveState := setTempState(dsInternalCalc);
  try
    RefreshInternalCalcFields(ActiveBuffer);
  finally
    RestoreState(saveState);
  end;
  if needsPost then
    Post;
end;

This can easily be expanded for normal calculated fields using CalculateFields. Although this shouldn't be necessary as calculated fields are recalculated whenever any other data field changes.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • I tried this and get *"[dcc32 Error] dMyDataModule.pas(151): E2389 Protected member 'TDataSet.RefreshInternalCalcFields' is inaccessible here"*. – GolezTrol Jun 16 '15 at 08:16
  • @GolezTrol, looks like a bug. Works in XE3, but not in XE7 and XE8 (currently cannot check for XE4 to XE6). Other protected members are perfectly accessible: "setTempState", "RestoreState". Just reported as RSP-11337. – Uwe Raabe Jun 16 '15 at 10:23
  • To narrow it down, I was on XE5. Thanks for testing! I've solved it now using the more classic class helper: typecasting to a locally defined `THackClientDataSet = class(TClientDataSet)`. – GolezTrol Jun 16 '15 at 10:53
-1

This is a bit of a hack, but it works!

DBGrid.Height := 30; 
DBGrid.Height := 200; // Refresh all Rows after first
CalculatedProc(DataSet); // Refresh first calculated fields. (Write name of your calculate procedure)
Gilles Gouaillardet
  • 8,193
  • 11
  • 24
  • 30
Ivan Ko
  • 9
  • 2