4

I'm trying to write an exception handler that displays user friendly messages. I don't know how to get the "Newly Entered" data value that caused the TDBGridInplaceEdit error.

For example:
I have a DBGrid loaded with data. When I intentionally change the PartNo field of row #1 to a non numeric value to cause a TDBGridInplaceEdit error... (From: 1313.3 To: 1313..3) ... I trap the error and display a message but I can't figure out how to get the bad '1313..3' value.

enter image description here
Original PartNo: 1313.3

enter image description here
Changed PartNo: 1313..3 (two decimal points)

enter image description here
Displayed Error Message from the Application onException

procedure TMain.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
  str : string;
begin
  str := sender.ToString;
  str := str + #10;
  str := str + RzDBGrid2.SelectedField.FieldName;
  str := str + #10;
  str := str + VarToStr(RzDBGrid2.SelectedField.Value);
  str := str + #10;
  str := str + e.Message;
  showmessage(str);
  //Application.ShowException(E);
end;

I'd like to format my own message using the bad '1313..3' value that was entered. How do you get this value?

  • I didn't read carefully enough before answering (I've deleted the answer now). You're getting an EConvertError, which is happening during the process of assigning the value, and therefore is actually failing in the RTL itself and will never make it to the point you can get it via `NewValue`. I don't know of any way to get to the value at that point. The best way would probably be to set an edit mask on the field itself, which would prevent the entry of the invalid value in the first place. – Ken White Aug 23 '13 at 00:47
  • I suppose I could parse the e.Message and harvest the value that way :) – Michael Riley - AKA Gunny Aug 23 '13 at 00:54
  • Yeah, I suppose. ;-) I think I'd prevent the invalid entry instead, though; it's less "hackish". If you do decide to go that route, you'll have to test for a specific Exception type (EConvertError), and then parse to see what *kind* of conversion (integer, floating point, or something else) to know what to parse, and also add handling for other types of exceptions (eg., missing required field, data truncation error (string too long), etc) and handle them as well separately, and then a generic handler for the ones you miss. – Ken White Aug 23 '13 at 01:11
  • 1
    Since the error will not necessarily occur form the cxGrid, but might come from every control bound to the field, I'd prefer catching the problems in the SetText of the field, e.g. procedure TForm1.ADataSetAFloatFieldSetText(Sender: TField; const Text: string); var f:Double; begin if not TryStrToFloat(Text,f) then begin raise Exception.Create('Error on: ' +Sender.FieldName + #13#10 + Sender.AsString + #13#10 + Text); end; end; – bummi Aug 23 '13 at 06:59
  • @bummi would you really raise an exception in a UI handler? – J... Aug 23 '13 at 10:23
  • @J... inspecting the code of `procedure TField.SetEditText(const Value: string); in DB.pas`, I'd expect this as one of the desired uses for `OnSetText` – bummi Aug 23 '13 at 10:35
  • @bummi I agree it's an appropriate place to catch the problem, I'm just wondering what the point of raising an exception is here since there is essentially a guarantee that there will never be an enclosing `try` block to handle it. – J... Aug 23 '13 at 10:43
  • @J... invalid data can't be assigned anyway. So you could handle it here with `repairing` the input, asking the user for corrected input or just Raise the Error with the format you wish instead of waiting for the exception which will occur. – bummi Aug 23 '13 at 10:49
  • @bummi - I'd like to use your technique but I don't know how to write the code. Would you please provide an answer (not comment) with example code I can use. Thank you. – Michael Riley - AKA Gunny Aug 23 '13 at 20:38

1 Answers1

5

If you are persisting the fields of your dataset, you can define an OnSetText Method on your fields.(Doubleclick on the dataset and choose add fields).

enter image description here

The Method could look like this:

procedure TForm1.ADataSetAFloatFieldSetText(Sender: TField; const Text: string);
var
 f:Double;
begin
  if not TryStrToFloat(Text,f) then
      begin
       raise Exception.Create(
                              'Error on: '
                              + #13#10'Dataset: '   + Sender.DataSet.Name 
                              + #13#10'Field: '     + Sender.FieldName 
                              + #13#10'Old Value: ' + Sender.AsString 
                              + #13#10'New Value: ' + Text
                             );
      end;
end;

If you want to avoid persisting your fields you can dynamically assign the Method to the field, e.g. after opening the Dataset.

procedure TForm1.ADataSetAfterOpen(DataSet: TDataSet);
Var
 i:Integer;
begin
  for I := 0 to Dataset.FieldCount - 1 do
      begin
        if Dataset.Fields[i].DataType in [ftFloat, ftCurrency, ftBCD] then
           Dataset.Fields[i].OnSetText := ADataSetAFloatFieldSetText;            
      end;
end;
bummi
  • 27,123
  • 14
  • 62
  • 101
  • 1
    ∞ +1 - Thank you for your example. This is absolutely brilliant! I never would have know this existed. How did you develop this much knowledge about Delphi? What resources do you recommend for gaining in-depth knowledge? – Michael Riley - AKA Gunny Aug 23 '13 at 22:39
  • 1
    Just looking interested at the properties and events (source is included), reading in Q&A's or forums, practising, but although the answer may be useful, it's far away from being brilliant. – bummi Aug 23 '13 at 22:49