Question

I have an in-memory dataset attached to a TDBGrid via some datasource. The problem is that an AV (validation error) is fired whenever the user enters a minus sign in a numeric field and presses either ENTER or navigates to another record. I am not able to catch the AV from either TDBGrid nor the TClientDataSet (OnEditError, OnPostError).

Am I doing something wrong here. If not, does anyone have a workaround this?

Using Delphi XE2 Enterprise, 4th update; latest MIDAS.DLL; target platform: Win64.

==============================================================================

Please try the following:

1- Create a new VCL application/form, and add the objects: TClientDataSet, a TDBGrid, and a TDataSource to the form. The ClientDataSet is not attached to any database (in-memory)

2- Add a persistent numeric field to Table1 (the TClientDataSet).

3- Link the above three objects. The persistent field's column should appear on the TDBGrid.

4- In your main form's OnShow method add the following lines:

Table1.Active := False;

Table1.CreateDataSet;

Table1.Active := True;

5- Run your program and type a minus sign in the numeric field column then press Enter.

Check the AV and then try adding an OnEditError and/or OnPostError event handlers to Table1 to catch the AV. You cannot abort the post. Meaning, the AV will happen, you can only catch it using the Application.OnException handler. This is not nice. You should be able to do something at the level your program is running at supposing your application contains many forms and routines, not at a higher level.

I hope I made my question clearer.

Was it helpful?

Solution

As Ken says, it's difficult to help you without sample code. So I won't be able to answer your question directly, but with a few pointers you may be able to figure it out for yourself. So I'm going to try teach you to fish. ;)

I do have a fair bit of experience with TClientDataSet in Delphi 3 and 6. I doubt things have changed all that much. Plus I'm going to focus more on techniques to kick-start you in the right direction.

First, make sure you compile with Debug DCU's so you can get breakpoints in VCL code. Don't be afraid to read and step through that code - it's a great way to learn. Plus you get to see Borland/CodeGear/Embarcadero's mistakes in all its ugly glory.

I'm assuming you already have Stop on Exceptions enabled (hence why you know you're specifically getting AV errors).

Do your test. When you get your AV, you'll probably come out in DbClient.pas (or perhaps one of the lower level units).

Work your way through the call stack looking for a place where you're within the try part of a try..except block that either swallows the Exception or short-circuits to another handler and then swallows it.

NOTE: DbClient.pas is littered with quite a few instances of the following bad code:

except //swallowing or squashing exceptions is 
end;   //very dangerous and should be avoided.

except
  //short-circuiting to Application.HandleException (usually informs the user)
  //but is not much better. The point is the rest of the program up the 
  //call-stack remains blissfully unaware that something went wrong.
  Application.HandleException(Self);
  Action := raAbort;
end;

Side comment: Just because Borland did it, doesn't mean it's good practice. You and many others have and will be burned by this.

Once you've found that, you're half-way there. You now know why you're not being informed of the AV.
Now, somewhere within the code that forms part of that call-stack will be a clue to how you're expected to handle the error. You could also place a breakpoint a little earlier in the VCL code so you can step through the to see the sequence of events leading up to the Exception. Since you specifically mentioned AV (Access Violation), look for objects that reference nil.

One example of what could be causing the problem is in the following snippet from D5 DbClient.pas

    FOnReconcileError(DataSet, E, UpdateKind, Action);
  finally
    E.Free;
  end;
except
  Application.HandleException(Self);
  Action := raAbort;
end;

You can see from the above that certain situations require you to implement an OnReconcileError event handler. Without it, the dll won't be given a callback into the above code snippet, and many errors are quietly kicked under the carpet, leaving you scratching your head.

My experience with TClientDataSet is that there were a number of little extra things you had to do to use it "correctly". This was not particularly obvious in the documentation. Though once you figured out all the bits and pieces and pieces you were supposed to do, the examples of how to do them were a little better.

Unfortunately my memory is a little fuzzy as to what all these things were. So this is a verrry rough guide, probably missing quite a bit.

  • Consider using an OnReconcileError event handler.
  • Consider implementing an UpdateProvider object.
  • You mentioned using it as an in-memory dataset, so you're probably using CreateDataSet correctly. But if I remember correctly you had 2 options for specifying the fields, and some of the finer detail attributes were a little picky.
  • Unrelated Tip : Be careful with the length of your string fields, TClientDataSet used to (and may still) horde memory in a similar fashion to short strings.

EDIT

Based on additional information in the question and using the technique described above, you are getting an EDatabaseError thrown from the following method.

procedure TIntegerField.SetAsString(const Value: string);
var
  E: Integer;
  L: Longint;
begin
  if Value = '' then Clear else
  begin
    Val(Value, L, E);
    if E <> 0 then DatabaseErrorFmt(SInvalidIntegerValue, [Value, DisplayName]);
    SetAsInteger(L);
  end;
end;

Higher up the call stack, you get the following method which hints at how to solve the problem.

procedure TField.SetEditText(const Value: string);
begin
  if Assigned(FOnSetText) then FOnSetText(Self, Value) else SetText(Value);
end;

Also, if you follow the call stack all the way up to the KeyPress, you'll notice that NO dataset or grid code is involved at all.

The reason your attempts to handle the error in OnPost error didn't work is that you're not getting anywhere near an attempt to Post your record. You're getting a Field Validation error, and the place to handle that is on the field.

Getting back to the hint how to solve the error - you would have to implement a OnSetText event for the field.

HOWEVER I would advise against doing so. The out-the-box behaviour is perfectly acceptable! The user gets an error message explaining what they did wrong, and they get the opportunity to fix it and try again. If they change their mind, they can hit Escape and cancel the edit.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top