8

how can I find out if an data-aware component field has been modified when the dataset is already in Insert state? I want to know if a field was 'really' modified. (I don't care if the user has input something in a field and after that erase everything, this mean that a modification occured).

DataSet.Modified, DataSet.UpdateStatus or ChangeCount are not solving my problem.

LE: let me explain more in depth this. so, initial dataset looks like

-------------------------------------
|PK  | Field1| Field2| Field3|Field4|
-------------------------------------
| 1  |  a    | b     | c     | d    |  
-------------------------------------

after insert

-------------------------------------
|PK  | Field1| Field2| Field3|Field4|
-------------------------------------
| 2  |       |       |       |      |  
-------------------------------------
| 1  |  a    | b     | c     | d    |  
-------------------------------------

when the dataset is really modified

-------------------------------------
|PK  | Field1| Field2| Field3|Field4|
-------------------------------------
| 2  | avalue|       |       |      |  
-------------------------------------
| 1  |  a    | b     | c     | d    |  
-------------------------------------
RBA
  • 12,337
  • 16
  • 79
  • 126
  • What about to ask directly the control ? I mean e.g. `TDBEdit.Modified` ? Take it as a lame note, please, I'm not a DB aware controls user :-) – TLama Apr 04 '12 at 15:18
  • 1
    @TLama - the problem is that, this is contained by a master form/frame heavily used. So, I'm trying to find a generic solution, which can indicate me if the dataset 'really' changed when is already in an insert/edit mode... – RBA Apr 04 '12 at 15:26
  • 2
    There is no dataset level way to do this. Only a data aware control way to do this. – Warren P Apr 04 '12 at 22:16

6 Answers6

8

You could hack the DataSet to change it's Modified property on AfterInsert/AfterEdit (and set initial/default values) and later test for DataSet.Modified (e.g. on before post).
To determine which specific fields were modified, I hold a copy of the initial record e.g.:

type
  TDataRecord = array of record
    FieldName: string;
    Value: Variant;
  end;

type
  TForm1 = class(TForm)
    ... 
  private
    FInitRecord, FPostRecord: TDataRecord;
  end;

function GetDataRecord(DataSet: TDataSet): TDataRecord;
var
  I: Integer;
begin
  Result := nil;
  if Assigned(DataSet) then begin
    SetLength(Result, DataSet.FieldCount);
    for I := 0 to DataSet.FieldCount - 1 do begin
      Result[I].FieldName := DataSet.Fields[I].FieldName;
      Result[I].Value := DataSet.Fields[I].Value;
    end;
  end;
end;

type
  TDataSetAccess = class(TDataSet);

procedure TForm1.ADODataSet1AfterInsert(DataSet: TDataSet);
begin
  // set initial values 
  ADODataSet1.FieldByName('PK').Value := GetMyPKValue;
  ADODataSet1.FieldByName('DateCreated').AsDateTime := Now(); 
  // un-modify
  TDataSetAccess(ADODataSet1).SetModified(False);
  // save initial record
  FInitRecord := GetDataRecord(ADODataSet1);
end;    

procedure TForm1.ADODataSet1BeforePost(DataSet: TDataSet);
var
  I: Integer;
begin
  if ADODataSet1.Modified then
  begin
    FPostRecord := GetDataRecord(ADODataSet1);
    Memo1.Lines.Clear;
    for I := 0 to Length(FPostRecord) - 1 do begin
      if FPostRecord[I].Value <> FInitRecord[I].Value then
        Memo1.Lines.Add(Format('Field %s was modified', [FPostRecord[I].FieldName]));
    end;
  end;
end;

Well, It's the abstract idea anyway. You could sub-class your TDataSet like I do, and implement this feature directly inside your TDataSet component.

kobik
  • 21,001
  • 4
  • 61
  • 121
3

When the state is dsInsert use:

VarCompareValue(Field.NewValue, Unassigned) = vrNotEqual;

dsEdit use:

OldValue <> Value;

Do not use this in dsInsert state as in numeric Fields 0 is equal Unassigned:

Field.NewValue <> Unassigned
Fabio Gomes
  • 5,914
  • 11
  • 61
  • 77
  • 1
    this means to verify is the field is not null...variant null – RBA Apr 04 '12 at 16:14
  • Didn't quite get your comment, the code I posted have to be verified in the actual record, usually in the BeforePost event, it will tell you if it was "really" modified. After the Post event just check for the ChangeCount. – Fabio Gomes Apr 04 '12 at 16:23
0

Until now I found a solution, which seems to work. Solution consist in: - link a datasource to the tdataset descendant - a global boolean variable is set to false on the dataset OnAfterScroll event, and true on the datasources's OnDataChange event.

From the tests I performed on the application until now, it seems that this work-around is working. I need now to investigate all the events which occur and need special treatment for not altering the state of the global variable in the process.

Any other ideas are welcomed

RBA
  • 12,337
  • 16
  • 79
  • 126
0
//The tag from the dataset will be 1 if the dataset has become dirty :)

type
  Tsomeform = class(TForm)
  //...
  MyDataset: TClientDataSet;
private
  afs: array of TFieldSetTextEvent;
public
  procedure SetIsDirty(Sender: TField; const Text: string);
end;

procedure Tsomeform.FormCreate(Sender: TObject);
begin
  SetLength(afs,MyDataset.FieldCount);
end;

procedure Tsomeform.FormShow(Sender: TObject); 
var i: integer;
begin
  for i := 0 to MyDataset.FieldCount-1 do
  begin
    afs[i] := MyDataset.Fields[i].OnSetText;
    MyDataset.Fields[i].OnSetText := SetIsDirty;
    MyDataset.Fields[i].Tag := i;
  end;
end;

procedure Tsomeform.MyDatasetBeforeInsert(DataSet: TDataSet);
    begin
      MyDataset.Tag := 0;
    end;
    
    procedure Tsomeform.MyDatasetBeforeEdit(DataSet: TDataSet);
    begin
      MyDataset.Tag := 0;
    end;
    
    procedure Tsomeform.SetIsDirty(Sender: TField; const Text: string);
    begin
      //Execute any potential TFieldSetTextEvent in any field of MyDataset
      if Assigned(afs[Sender.Tag]) then
        afs[Sender.Tag](Sender,Text);
      if VarToStr(Sender.Value)<>Text then 
        MyDataset.Tag := 1;
      Sender.Value := Text;//It is important to enabling the change.
    end;
  • Sorry I would have to set the tag to 0 and to OnBeforeEdit of course :) – Branko Radošević May 06 '22 at 14:33
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 07 '22 at 15:03
0
procedure Tsomeform.MyDatasetBeforeInsert(DataSet: TDataSet);
begin
  MyDataset.Tag := 0;
end;

procedure Tsomeform.MyDatasetBeforeEdit(DataSet: TDataSet);
begin
  MyDataset.Tag := 0;
end;

procedure Tsomeform.SetIsDirty(Sender: TField; const Text: string);
begin
  if VarToStr(Sender.Value)<>Text then 
    MyDataset.Tag := 1;
end;

procedure Tsomeform.FormShow(Sender: TObject); 
var i: integer;
begin
  for i := 0 to MyDataset.FieldCount-1 do
  begin
    MyDataset.Fields[i].OnSetText := SetIsDirty;
  end;
end;
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 09 '22 at 01:26
0
//The tag from the dataset will be 1 if the dataset has become dirty :)

type
  Tsomeform = class(TForm)
  //...
private
afs: array of TFieldSetTextEvent;
public

end;

procedure Tsomeform.FormCreate(Sender: TObject);
begin
  SetLength(afs,MyDataset.FieldCount);
end;

procedure Tsomeform.FormShow(Sender: TObject); 
var i: integer;
begin
  for i := 0 to MyDataset.FieldCount-1 do
  begin
    afs[i] := MyDataset.Fields[i].OnSetText;
    MyDataset.Fields[i].OnSetText := SetIsDirty;
    MyDataset.Fields[i].Tag := i;
  end;
end;

procedure Tsomeform.MyDatasetBeforeInsert(DataSet: TDataSet);
    begin
      MyDataset.Tag := 0;
    end;
    
    procedure Tsomeform.MyDatasetBeforeEdit(DataSet: TDataSet);
    begin
      MyDataset.Tag := 0;
    end;
    
    procedure Tsomeform.SetIsDirty(Sender: TField; const Text: string);
    begin
      //Execute any potential TFieldSetTextEvent in any field of MyDataset
      if Assigned(afs[Sender.Tag]) then
        afs[Sender.Tag](Sender,Text);
      if VarToStr(Sender.Value)<>Text then 
        MyDataset.Tag := 1;
      Sender.Value := Text;//It is important to enable change
    end;