I have implemented an editable DBGrid. If a field is not filled correctly an exception is thrown and a message is shown, for example:
'08:00::00' is not a valid time
How can I catch those exceptions so that I can show messages written by me instead of the automatically generated ones? I would be thankful for any help.
As #teran pointed in his comment, the exception is raised by the TDataSet (or one of it's components) that is bind to the TDBGrid, or by the DB engine itself.
You can try to handle the TDataSet.OnPostError (see also OnUpdateError and OnEditError):
TDataSet.OnPostError: Occurs when an application attempts to modify or insert a record and
an exception is raised. Write an OnPostError event handler to handle
exceptions that occur when an attempt to post a record fails.
Note that you could always use Application.OnException global event handler to catch any EDBxxx exceptions in your application.
EDIT: The EConvertError exception is raised before any actual data modifications, or any Post operation by the TDateTimeField field i.e.:
0045af91 +085 Project1.exe SysUtils StrToDateTime <- EConvertError
004ab76a +042 Project1.exe Db TDateTimeField.SetAsString
004a9827 +007 Project1.exe Db TField.SetText
004a95d9 +029 Project1.exe Db TField.SetEditText
004d6448 +014 Project1.exe DBGrids TCustomDBGrid.UpdateData
004d087f +02b Project1.exe DBGrids TGridDataLink.UpdateData
004d599a +01a Project1.exe DBGrids TCustomDBGrid.MoveCol
StrToDateTime is throwing the exception inside TDateTimeField.SetAsString, not touching the data, and
the TDataSet.OnxxxError event handlers will not be fired at all.
So your choices are (test the application in release mode):
1.Intercept and handle EConvertError via Application.OnException.
2.Use TField.EditMask to restrict user input to a valid time format e.g. !90:00;1;_ or use inplace DateTimePicker editor inside your Grid. (and avoid catching this exception).
3.Override TDateTimeField: use persistent fields with your TDataSet and create an inter-poser class as such:
type
TDateTimeField = class(Db.TDateTimeField)
protected
procedure SetAsString(const Value: string); override;
end;
TForm1 = class(TForm)
...
procedure TDateTimeField.SetAsString(const Value: string);
begin
try
inherited SetAsString(Value);
except
on E: EConvertError do
begin
ShowMessage(E.Message);
Abort;
end;
end;
end;
If an exception is being raised then there should be two error messages being shown when run in the debugger. One of these will be caught by the debugger and the 2nd handled by the UI (when running your program as a user you will only see the 2nd).
The exception error message should have contain a string like
Appname.exe raised exception EExceptionName with message XXX
You will need to take note of EExceptionName.
Around the block of code creating the exception you will need to write
...
try
code that can cause the exception here
except
on e: EExceptionName do
begin
ShowMessage('Your apps nicer error message here');
end;
end;
note - if you don't make a call to exit; after handling an exception, your code will continue executing everything after your try..except block. Also, if there are many things that can cause the same error message in the same code block then you may not be able to write anything too specific. e.Message is a string that holds the message that is shown in the unhandled exception and may be useful to also present to the user.
Also try to move away from BDE - ADO is far better supported on modern systems.
Related
By default any unhandled exception occurring in TForm.OnCreate will lead to an error message beeing shown instead of an exception beeing thrown.
The reason is this code in VCL.Forms:
function TCustomForm.HandleCreateException: Boolean;
begin
Application.HandleException(Self);
Result := True;
end;
All my forms inherit from TMyForm. I plan to override this function to solve the problem:
function TMyForm.HandleCreateException: Boolean;
begin
Result := False;
end;
But I cannot imagine anybody wanting an exception on form creation to be swallowed, leaving a potentially halfly initialized form, yet this code still exists in VCL. This leaves me with the question:
Are there any reasons to not treat unhandled exceptions on form creation this way? Or are there better options to handle my problem?
Edited to clarify the original problem. I have the following code:
try
//...
Frm := TBooForm.Create(...);
Frm.ShowModal;
//...
except
//exception in TBooForm.FormCreate not landing here.
end;
If I don't touch global exception handling, an unhandled exception in TBooForm.FormCreate() will not land in the exception block. Instead Frm will display in halfly initialized state, leading to hard to track errors.
I want to log every exception raised in the delphi application.
For that, I overrided the Application.OnException event with one of my own in the project source code.
program Project;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Logger in 'Logger.pas',
JCLDebugHandler in 'JCLDebugHandler.pas';
{$R *.res}
begin
Application.Initialize;
Application.OnException := TApplicationException.AppException;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
This works perfectly, but I´m failing at catching with this solution the exceptions catched in a try-except block.
When the exception is catched in the except block, it just not triggers the Application.OnException event.
Is there any way to first catch it in the Application.OnException event rather than the except block?
The Application.OnException handler is only called for unhandled exceptions.
An unhandled exception is one where no try..except block has caught the exception or where it has been caught and then re-raised.
Using some trivial examples to demonstrate, let's assume that this is virtually the only code in the application and that there are no other exception handlers...
try
a := 42 / 0;
except
on EDivisionByZero do
begin
Log.i('Silly division by zero error has been logged');
raise;
end;
end;
In this case, the exception is caught but the application has no strategy for handling the exception so simply logs that it has occurred and then re-raises the exception. Execution will continue in any outer except block. If there are none, or if any that may exist also re-raise the exception, then eventually the exception will reach the Application.OnException handler.
But an exception handler might not need to re-raise an exception:
try
a := 42 / 0;
except
on EDivisionByZero do
a := 0;
end;
In this case, the exception handler handles the division by zero and does not re-raise it since the code is happy to proceed with a result of zero in that case (unlikely, but this is just an example).
Since the exception is not re-raised, execution proceeds (after the try..except block) as if the exception had never happened in the first place. Your Application.OnException will never know about it.
In summary: Application.OnException is your last chance to deal with an unhandled exception. It is not the first opportunity to respond to any exception.
Intercepting exceptions at the moment that they occur, before any application code has had an opportunity to react or deal with them is possible, but is pretty advanced stuff and no simple mechanism is provided "out of the box".
Fortunately there are 3rd party libraries you can use that may provide the capabilities you are seeking to introduce into your application.
A popular one for Delphi which you might wish to check out is madExcept.
I finally used JCL for capturing and logging all my exceptions. I created a new .pas file with the following code:
/// <summary>
/// Inicializa JCL para capturar la informacion necesaria de la excepcion.
/// </summary>
initialization
JclAddExceptNotifier(SetExceptionError);
JclStackTrackingOptions := JclStackTrackingOptions + [stExceptFrame];
JclStartExceptionTracking;
/// <summary>
/// Finaliza la notificacion de JCL luego de capturar la informacion necesaria de la excepcion.
/// </summary>
finalization
JclRemoveExceptNotifier(SetExceptionError);
end.
With this code, I can capture the exception and treat it in my function SetExceptionError. I was avoiding using some 3rd party framework for this simple logging.
Generally I make my exception handlers handle only very specific exceptions, so that the code doesn't try to recover from exceptions that weren't anticipated and potentially aren't recoverable.
But there are a few places where it is impossible to know ahead of time what exceptions could legitimately come up from which recovery is perfectly possible (because of calls to libraries that don't specify what exceptions they might raise). So in these special cases I do in fact trap all exceptions: on e:Exception ....
But this also handles EAssertionFailed exceptions, which really should never be handled because they imply incorrect code.
So I've started writing exception handlers like this:
on e:Exception do
begin
if e is EAssertionFailed then
begin
raise;
end;
…
This just seems ugly and error-prone. (What if I forget the if e is EAssertionFailed code?)
Is there a better way? (Should I just never ever use on e:Exception …, except in a top level handler that aborts the program?)
Update
I think David's comment below is correct - never use a blanket exception handler (other than to log the exception and stop the program).
The problem then becomes "How can I get a list of all exception types that are safe to handle arising from some arbitrary bit of code?" I don't think this is one that Stack Overflow can answer.
(The particular problem is a 3rd party library that reads a configuration file. There are fairly obvious exceptions for filing system errors, but the library seems to raise numerous and almost random exception types for syntax errors within the file itself. I don't think I could ever be sure that I've exhausted the possibilities. I certainly cannot have my application fall over because the configuration file couldn't be read!)
The standard Delphi exception handling pattern is
try
// Some code which may raise an exception
except
on E: SomeError do begin
// Handle SomeError and derived classes
end;
on E: SomeOtherError do begin
// Handle SomeOtherError and derived classes
end;
...
end;
it does not require the use of on E:Exception ….
If you don't know the exception type you should not handle it; or sometimes (ex in DLL code) you should handle ALL exceptions.
Why don't to use this:
try
...
except
on EAssertionFailed do raise;
on E: .... do ... ; // your exception
on E: .... do ... ; // your exception
on E: .... do ... ; // your exception
on E: Exception do ... ; // all other exceptions
end;
If any of the handlers in the exception block matches the exception, control passes to the first such handler. An exception handler 'matches' an exception just in case the type in the handler is the class of the exception or an ancestor of that class.
Or you can remove on EAssertionFailed do raise; and on E: Exception do ... ; from the exception block and then exception handler will be searched in the next-most-recently entered try...except statement that has not yet exited.
I'm offering my own answer here to see what the community thinks.
The point wasn't a misunderstanding of how exception handling works in Delphi. I've summarised the particular problem in my update to the question.
My solution is going to be to convert the many different exception types the library can raise into a single type that I can handle explicitly. I'll put a blanket exception handler around the one problem call to the library. This handler will raise a new exception of a known type, to be caught explicitly higher up at the appropriate time.
(And no, the exceptions raised by the library call do not have any common base type other than Exception itself, so I really do have to catch all exceptions if I want to recover from a failure in the library call.)
Crucially, I believe that although I cannot hope to enumerate all the exceptions that this library call could raise, I can be sure that it won't put the application into an unstable state when it raises an exception.
Update
In the end I was able to use something like this:
on e:Exception do
begin
if (e.ClassType = Exception) or (e is EConvertError) or
(e is EVariantError) then
begin
ShowMessage('The application settings could not be loaded.');
end
else
begin
// An exception we didn't anticipate. Assume it is fatal.
raise;
end;
end;
A lot of the recoverable exceptions raised by the library are of type Exception, but note that only Exception and the specified descendants are handled. The others are raised indirectly by the runtime library. There might turn out to be other exceptions that should be handled too, but this seems like a good start. It's better to have the application bail out when it needn't have done so than to continue running and corrupt the user's data.
I am a new programmer and trying to understand how Delphi's Application.OnException event works. A colleague has modified the default exception handling by creating his own method and instantiating it and passing it to Application.OnException in the initialization section of a unit. This unit is declared in the uses clause of another unit and otherwise unused.
unit ADLDebug;
...
class procedure TADLExceptionHandler.ADLHandleException (Sender: TObject; E: Exception);
...
initialization
Handler := TADLExceptionHandler.Create;
Application.OnException := Handler.ADLHandleException;
I can only step into the initialization section using the debugger, and not into the ADLHandleException method. I am trying to cause an exception in the code that will be caught by the redefined HandleException method.
Should the scope of the redefined HandleException method be only in the units that include ADLDebug? I was thinking that it should be application wide, but I can't seem to call it.
The scope of Application.OnException is indeed application wide. The event will fire whenever an exception is raised that is not handled.
You are failing to see the event fire because you are raising the exception at startup, before the message loop starts.
In our application we have created several Exceptions classes for all our needs. But now the problem is, that the raised Exception Dialog always is MessageType mtError and of course shows the mtError-Icon.
For a few of our Exceptions i would prefer a less agressive Icon/MessageType, for example MessageType mtInformation.
Is there any way to change the Icon directly in the Exceptionclass without raising and catching it again with try...except on each occurence?
You will need to customise the top level exception handler.
Write an event handler and attach it to Application.OnException. You can then handle particular exceptions any way you like.
The event handler will look like this:
procedure TMainForm.ApplicationException(Sender: TObject; E: Exception);
begin
if E is EMyException then
ShowExpectedMessageBox(E)
else
Application.ShowException(E);
end;
Obviously you have to write the code for ShowExpectedMessageBox. This can show a message box using whatever icon you prefer.
The OnException event is called when an unhandled exception reaches the message loop. If you don't have an event handler, the framework calls Application.ShowException. Which is what you should do for those exceptions that do not get your special treatment.