In the AfterPost event handler for a ClientDataSet, I need the information if the ApplyUpdates function for the current record will do an update or an insert.
The AfterPost event will be executed for new and updated records, and I do not want to declare a new Flag variable to indicate if a 'update' or ' insert' operation is in progress.
Example code:
procedure TdmMain.QryTestAfterPost(DataSet: TDataSet);
begin
if IsInserting(QryTest) then
// ShowMessage('Inserting')...
else
// ShowMessage('Updating');
QryTest.ApplyUpdates(-1);
end;
The application will write a log in the AfterPost method, after ApplyUpdate has completed. So this method is the place which is closest to the action, I would prefer a solution which completely can be inserted in this event handler.
How could I implement the IsInserting function, using information in the ClientDataSet instance QryTest?
Edit: I will try ClientDataSet.UpdateStatus which is explained here.
ApplyUpdates doesn't give you that information - since it can be Inserting, updating and deleting.
ApplyUpdates apply the change information stored on Delta array. That change information can, for example, contain any number of changes of different types (insertions, deletions and updatings) and all these will be applied on the same call.
On TDatasetProvider you have the BeforeUpdateRecord event (or something like that, sleep does funny things on memory :-) ). That event is called before each record of Delta is applied to the underlying database/dataset and therefore the place to get such information... But Showmessage will stop the apply process.
EDIT: Now I remembered there's another option: you can assign Delta to another clientdataset Data property and read the dataset UpdateStatus for that record.
Of course, you need to do this before doing applyupdates...
var
cdsAux: TClientDataset;
begin
.
.
<creation of cdsAux>
cdsAUx.Data := cdsUpdated.Delta;
cdsAux.First;
case cdsAux.UpdateStatus of
usModified:
ShowMessage('Modified');
usInserted:
ShowMessage('Inserted');
usDeleted:
ShowMessage('Deleted'); // For this to work you have to modify
// TClientDataset.StatusFilter
end;
<cleanup code>
end;
BeforeUpdateRecord event on TDataSetProvider is defined as:
procedure BeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet; DeltaDS:
TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied: Boolean);
Parameter UpdateKind says what will be done with record: ukModify, ukInsert or ukDelete. You can test it like this:
procedure TSomeRDM.SomeProviderBeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied: Boolean);
begin
case UpdateKind of
ukInsert :
// Process Insert;
ukModify :
// Process update
ukDelete :
// Process Delete
end;
end;
Note: this event signature is from Delphi 7. I don't know if it changed in later versions of Delphi.
Set the ClientDataSet.StatusFilter to an TUpdateStatus value, and then read ClientDataSet.RecordCount
for example,
ClientDataSet1.StatusFilter := [usDeleted];
ShowMessage(IntToStr(ClientDataSet1.RecordCount));
will return the number of Delete queries that will be executed.
Note two things, however. Setting StatusFilter to usModified always includes both the modified and unmodified records, so you take half of that value (a value of 4 means 2 Update queries will be executed). Also, setting StatusFilter to [] (an empty set) is how you restore to default view (Modified, Unmodified, and Inserted)
Make sure that any unposted changes have been posted before doing this, otherwise unposted changes may not be considered.
Related
I have TIBQuery-TDataSetProvider-TClientDataSet chain in Delphi 2009 (Firebird 3.0) and I execute MyClientDataSet.ApplyUpdates(0). Am I required to call CheckBrowseMode or Post on this CDS before calling ApplyUpdates(0). I am almost sure that I am required to call Post/CheckBrowseMode and I think that no-posted updates will not be applied to the IBQuery, I have no documentation for/against such thinking but it is logical to think so. But sometimes I can observe that MyClientDataSet is in [dsInsert, dsEdit] state before ApplyUpdates(0) and the new values are still posted and saved in query. But there are evidence and reason against that as well. So - I am confused.
Of course, I don't use CachedUpdated for TIBQuery (because there is CDS) and this is not the question about commiting of transactions, I strongly control them and that issue is excluded.
I made test: I put raise Exception in the BeforePost event of MyClientDataSet and MadException gives trace:
TMyDM.MyClientDataSetBeforePost
TDataSet.DoBeforePost
TDataSet.Post
TCustomClientDataSet.Post
TDataSet.CheckBrowseMode
TCustomClientDataSet.ApplyUpdates
So, there is empirical evidence, that CheckBroseMode is called automatically, but is it by accident (e.g. due to some special configuration of DataSetProvider or ClientDataSet) or is it rule that I can find in the documentation?
or is it rule that I can find in the documentation?
There is a simpler rule, which should be self-evident, I would have thought: If the CDS's state is dsEdit or dsInsert, one might think of it as being in a change-pending state - changes have made (e.g. in a DB-aware control) but not yet been written back to the CDS's record buffer (or buffers if it has nested data). Otoh, the point of calling ApplyUpdates is to write changes in the record buffer back to the source dataset via the DSP. It would seem elementary therefore that one should not attempt to call ApplyUpdates while the CDS is in dsEdit/dsInsert. In any case, CheckBrowse mode is called by TCustomClientDataSet.ApplyUpdates:
function TCustomClientDataSet.ApplyUpdates(MaxErrors: Integer): Integer;
var
RootDataset: TCustomClientDataset;
begin
CheckBrowseMode;
RootDataset := Self;
while RootDataset.FParentDataSet <> nil do
RootDataset := RootDataset.FParentDataset;
with RootDataset do
if ChangeCount = 0 then
Result := 0 else
Reconcile(DoApplyUpdates(Delta, MaxErrors, Result));
end;
It is worth noting that if a dataset's state is dsEdit or dsInsert, CheckBrowseMode does one of three things:
Nothing, except calling CheckActive and then DataEvent(deCheckBrowseMode, 0),
if the dataset's state is not dsEdit, dsInsert or dsSetKey
Calls Post if the current record had been modified
Calls Cancel otherwise.
procedure TDataSet.CheckBrowseMode;
begin
CheckActive;
DataEvent(deCheckBrowseMode, 0);
case State of
dsEdit, dsInsert:
begin
UpdateRecord;
if Modified then Post else Cancel;
end;
dsSetKey:
Post;
end;
end;
On the TDatasetProvider.OnBeforeUpdateRecord, how do I
access the source or originating clientdataset of the
sent DeltaDS parameter?
procedure TdmLoanPayment.dpLoanPaymentBeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied: Boolean);
var
sourceCDS: TClientDataset;
begin
sourceCDS := DeltaDS.???;
...
end;
I need to access some properties from the corresponding clientdataset. Setup is TSQLDataset/TDatasetProvider/TClientDataset.
Edit:
The cause of all this hassle is, I wanted to derive a component from TDatasetProvider and assign a default OnBeforeUpdateRecord.
I think SourceDS is what are looking for.
The Sender parameter identifies the provider that is applying updates.
The SourceDS parameter is the dataset from which the data originated.
If there is no source dataset, this value is nil (Delphi) or NULL
(C++). The source dataset may not be active when the event occurs, so
set its Active property to true before trying to access its data.
The DeltaDS parameter is a client dataset containing all the updates
that are being applied. The current record represents the update that
is about to be applied.
The UpdateKind parameter indicates whether this update is the
modification of an existing record (ukModify), a new record to insert
(ukInsert), or an existing record to delete (ukDelete).
The Applied parameter controls what happens after exiting the event
handler. If the event handler sets Applied to true, the provider
ignores the update: it neither tries to apply it, nor does it log an
error indicating that the update was not applied. If the event handler
leaves Applied as false, the provider tries to apply the update after
the event handler exits.
for example:
procedure TdmLoanPayment.dpLoanPaymentBeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied: Boolean);
begin
ShowMessage(TClientDataSet(SourceDS).Name); // get source name
...
end;
Edit
or
procedure TdmLoanPayment.dpLoanPaymentBeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied: Boolean);
begin
if SourceDS.Name = 'Name1'then
...do something ...
if SourceDS.Name = 'Name2'then
...do something ...
end;
If you trace out of the call to your DataSetProvider1BeforeUpdateRecord, you'll
see that the dataset passed as the SourceDS parameter is the Source dataset of
the UpdateTree, and that is, AFAICS, the dataset that the DataSet property of
the Provider is set to. Of course, this is not the CDS from which the Delta
has been derived (in my test case it's actually a TAdoQuery).
Looking at the source code in Provider.Pas, I can't immediately see a
way to find the identity of the Delta's source CDS. I don't think that is particularly surprising because the Provider's operation is invoked by a CDS and not vice versa, and all the data the Provider needs from the CDS is its Delta.
On the other hand, it's a pretty fair bet that the BeforeUpdateRecord event has
been triggered by the most recent, still-pending, call to ApplyUpdates on one of your CDSs, so if
you make a note of that in their BeforeApplyUpdates event(s), that will probably
tell you what you want to know. I'd expect that to work for a single-level update, but it might be more tricky if the UpdateTree is operating on nested CDSs.
If your CDSs all have individual Providers, but the providers share a BeforeUpdateRecord event, you could identify the CDS for a given provider using code like this:
function TCDSForm.FindCDSForProvider(DataSetProvider: TDataSetProvider):
TClientDataSet;
var
i : Integer;
begin
Result := Nil;
for i := 0 to ComponentCount - 1 do begin
if Components[i] is TClientDataSet then
if TClientDataSet(Components[i]).ProviderName = DataSetProvider.Name then begin
Result := TClientDataSet(Components[i]);
Exit;
end;
end;
end;
I have a cxGrid where I change the background color of some fields based on values in some of the fields.
This is all working very fine. But if I cahnge something in the grids data, the colors aren't updated before I close an reopen my form.
What procedure to call to get this updated if the record is changing?
To my experience it does update when you switch the row. But i used it in DB-mode with TClientDataSet.
Check methods like
TcxControl.InvalidateRect
TcxControl.InvalidateRgn
TcxControl.InvalidateWithChildren
You can also invalidate node:
TcxGrid.ActiveView.Invalidate;
TcxGrid.ViewData.Records[0].Invalidate;
TcxGridViewData.Rows[0].Invalidate
TcxCustomGridTableController.FocusedRecord.Invalidate;
Events like
TcxCustomGridTableViewStyles.OnGetContentStyle
TcxCustomGridTableItem.OnCustomDrawCell
also exposes those items (with their Invalidate methods) among or inside parameters, like
ARecord: TcxCustomGridRecord;
ViewInfo -> TcxGridTableCellViewInfo.GridRecord
In other words - open the cxTL unit and grep for "invalidate" word and note every match.
If your grid is attached to a data set, and the data in the dataset changes, the OnGetContentStyle events are called automatically. Make sure that your dataset knows that the data is updated. It sounds like your editing form isn't telling the grid dataset to refresh itself. You can do that either with a callback procedure or implementing the Observer Pattern.
The following code demonstrates how to implement an OnGetContentStyle event for a grid column.
procedure TFormWithGrid.cxGrid1DBTableView1LASTNAMEStylesGetContentStyle(
Sender: TcxCustomGridTableView; ARecord: TcxCustomGridRecord;
AItem: TcxCustomGridTableItem; var AStyle: TcxStyle);
begin
if ARecord.Values[cxGrid1DBTableView1FIRSTNAME.Index] = 'test' then
begin
AStyle := TcxStyle.Create(nil);
AStyle.Color := clRed;
AStyle.Font.Style := [fsBold];
end;
end;
in my situation, this will works
cxGridDBTblVwContenido.DataController.Refresh;
i downloaded a DBDateTimePicker which i am using to edit dates in a table, a start date and completion date. When appending to a table in normal edit boxes i use the following code before the append and post in order to prevent a completion date being set before a start date.
if (DTPAddStartDate.Date) > (DTPAddCompletionDate.Date) then
begin
raise Exception.Create ('The Completion date of a Job can not be before a Start Date');
abort;
end;
The problem is that i want to be able to achieve the same thing when editing dates via the DBDateTimePickers, if i have similar code on the BeforePost event or something then i will get premature error messages as the user may not have had a chance to edit the other related DBDateTimePicker. I have no idea how i could relate the two fields so that i can validate them together somehow or come up with another solution to the problem, any suggestions?
(Im using an adotable connected to access and the component was downloaded from the following link, http://apollosoft.net/jms/).
I guess you are using the TDBDateTimePicker from this page but hard to say, it's missing in your question at this time.
However you may use the field's OnValidate event which is being fired whenever the value is changed, but it's the stage when the data are going to be written do the database, so it's IMHO the unnecessary wasting of time.
You can validate the values before they tries to update the record, what is in the case of (almost ?)every DB aware control when you try to exit the control, so you may pretty easily add some validation event, something like OnCanUpdate to your date time picker.
Because of missing link to the source where did you get your component I'll assume you are using this TDBDateTimePicker. If you add to the DBDateTimePicker.pas file the following lines and rebuild your package where is installed, the new OnCanUpdate event will appear in the component's Object Inspector. This event if you use it has one important parameter Allowed, if you set it to True you will allow the component to update the record, if you set it to False (what is default) no data will be updated in your dataset. So it's the place where you can do the validation and decide you want to update record or not.
type
TDBDateTimePicker = class;
TOnCanUpdate = procedure(Sender: TDBDateTimePicker;
var Allowed: Boolean) of object;
TDBDateTimePicker = class(TDateTimePicker)
private
FOnCanUpdate: TOnCanUpdate;
procedure CMExit(var Message: TCMExit); message CM_EXIT;
published
property OnCanUpdate: TOnCanUpdate read FOnCanUpdate write FOnCanUpdate;
end;
procedure TDBDateTimePicker.CMExit(var Message: TCMExit);
var
Allowed: Boolean;
begin
if Assigned(FOnCanUpdate) then
begin
Allowed := False;
FOnCanUpdate(Self, Allowed);
if not Allowed then
begin
SetFocused(True);
Exit;
end;
end;
try
FDataLink.UpdateRecord;
except
SetFocus;
raise;
end;
SetFocused(False);
inherited;
end;
So the code in the OnCanUpdate event may looks like this (note you can use this event as common for both of your date time pickers).
procedure TForm1.DTPAddStartDateCanUpdate(Sender: TDBDateTimePicker;
var Allowed: Boolean);
begin
if (DTPAddStartDate.Date) > (DTPAddCompletionDate.Date) then
begin
// the Allowed parameter is in current code initialized to False
// when it comes into this event, so the following line has no
// sense here
Allowed := False;
// here you can display the error message or raise exception or just
// whatever you want, the record won't be modified in any way
Application.MessageBox('The completion date of a job cannot be before a ' +
'start date. The record won''t be modified ;-)', 'Date Input Error...',
MB_YESNO + MB_ICONSTOP + MB_TOPMOST);
end
else
// only setting the Allowed to True will actually allows the record to be
// updated, so if your validation passes, set the Allowed to True
Allowed := True;
end;
Caviate:
A shot in the dark here since the component isn't familiar. If you have the components in a form then the objects are member variables of the form. Assuming they are created when the form is initialized (you're using them so they are) simple check an event when they lose focus.
//Pseudo Code--I'm sure it is using a standard event model
myForm.DTPAddCompletionDate.OnChange(...)
begin
//do your check if it is successful something like...
if not (DTPAddStartDate.Date > DTPAddCompletionDate.Date) then
return;
else
someSubmitButton.enabled = true;
end;
I don't have Delphi in front of me and it has been years so the code probably won't match up perfectly. However, the idea should work. Just check the references.
Again I have a problem with the TClientDataSet.
I guess it's something really simple but I struggle on it for a while now.
Here's some code what shows what I want to do:
procedure TForm1.Button1Click(Sender: TObject);
begin
ClientDataSet1.Insert;
ClientDataSet1.FieldByName('anruf_von').AsDateTime := time;
ClientDataSet1.Post;
ClientDataSet1.ApplyUpdates(0); // without this applyUpdates in button2 works.
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
ClientDataSet1.edit;
ClientDataSet1.FieldByName('anruf_bis').AsDateTime := time;
ClientDataSet1.Post;
showmessage(intToStr(ClientDataSet1.ChangeCount)); // returns 1
if ClientDataSet1.ChangeCount > 0 then
ClientDataSet1.applyUpdates(0);
end;
The code is self explaining I think.
When I press button1, a record is created and after the call to applyUpdates its written to the databse.
When I press button2, I want to make a change to this record and apply the updates to the database - and that doesn't work.
But when I comment out the applyUpdates in button1, the applyUpdates in button2 works correctly.
Try to change Provider.UpdateMode to upWhereKeyOnly, and set key field in Provider.OnUpdateData.
My gues is that insert works always since it is executed as
INSERT INTO ATABLE (anruf_von, anruf_bis) VALUES (...)
But update fails, since WHERE part will match DB stored time with time from clientdataset.
In fact, you will probably try to match two doubles, which is a no-no.
UPDATE ATABLE SET anruf_bis=<Time>
WHERE anruf_von=<WRONG Time, with more precision than stored in db>
When you set UpdateMode to upWhereKeyOnly, generated SQL sholud look like this
UPDATE ATABLE SET anruf_bis=<Time>
WHERE ID=<ID Value>