Change Icon of a raised Exception in Delphi - delphi

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.

Related

Delphi -make a function available to other forms beside mainform

I use a function to log application errors to a file.I have it on my main form.How do I tell my other forms, in case of an error,to use this function? Just by chance I added this to my query on some other form:
......
try
ClientDataSet1.Execute;
ClientDataSet1.Close;
except
on E:Exception do begin
ShowMessage('Error : ' + E.Message);
LogError(E);
....
My LogError(E); is not recognized (gives error). Tried adding the function :procedure LogError(E:Exception); to public uses but wont work.
Structured Exception Handling
I'd like to point out that your approach to exception handling is very tedious and wasteful coding. Not to mention quite dangerous. You don't want to be littering your code with individual handlers to log your exceptions when there's a much more practical approach.
Fortunately Delphi provides the means to make this much easier. First a little background about structured exception handling... Most developers make the mistake of trying to deal with errors by writing code like the following:
try
//Do Something
except
//Show Error
//Log Error
end;
The problem with the above code is that it swallows the error. Even though the user sees an error message, the error itself is hidden from the rest of the program. This can result in other parts of the program doing things they should not do because of the error.
Imagine a program that calls the following routines: LoadAccount; ApplyDiscount; ProcessPayment;. But the the first routine raises an error. Which a programmer (mistakenly thinking they're being diligent) decided to "handle" as above. The problem is that the next two routines will still be called as if nothing is wrong! This could mean that a discount is applied to and payment processed for the wrong account!
The point is that structured exception handling saves us from these headaches (provided we don't break the way it works). If the LoadAccount routine didn't try "handle" the exception, then while the application is in an "exception state" the code would simply keep jumping out of routines until it finds a finally / except handler.
This suits an event driven program very nicely.
Suppose a user clicks a button that will cause your program to start a multi-step task: with loops, calls to child methods, creating objects etc. If anything goes wrong, you want to abandon the entire task and wait for the next input. You don't want to stubbornly keep trying to complete the task; because you'll either just get more errors, or worse: later changes will do the wrong thing because earlier required steps did not complete successfully.
Easy Logging of Errors
As mentioned earlier, if you simply leave an exception alone, the error will "bubble up" to an outer exception handler. And in a standard GUI application, that will be the default Application exception handler.
In fact at this stage, doing nothing special: if an exception is raised in the middle of the button click task described earlier, the default exception handler will show an error message to the user. The only thing missing is the logging. Fortunately, Delphi makes it easy for you to intercept the default Application exception handler. So you can provide your own implementation, and log the error as you desire.
All you need to do is:
Write a method using the TExceptionEvent signature.
E.g. procedure MyHandleException(ASender: TObject; E: Exception);.
Then assign your custom handler using: Application.OnException := MyHandleException;
General Logging
Of course, this only covers logging of exceptions. If you want to do any other ad-hoc logging, you want to be able to reuse the code. This basically requires you to move your logging code into a separate unit that all your other units can call as needed.
So putting these things together you might do something as follows:
TLogger = class
public
procedure WriteToLog(S: string); //Performs actual logging of given string
procedure HandleException(ASender: TObject; E: Exception); //Calls WriteToLog
end;
Then you might set it up in your program unit as follows:
begin
Logger := TLogger.Create(...);
Application.OnException := Logger.HandleException;
Logger.WriteToLog('Custom exception handler has been assigned.');
//Rest of application startup code
end;
Final Thoughts
I mentioned that if an unexpected exception occurs, you want to abandon the current task. But what happens if you've already done some things that should now be undone as a result of the error. Then you would simply implement it as follows:
try
//Do something
except
//Undo
raise; //NOTE: more often than not, one should re-raise an
//exception so that callers are aware of the error.
end;
Note that this is the primary use for exception handlers. Yes they can also be used to swallow exceptions. But they should only swallow an exception if it has been truly resolved.
While Delphi is object-oriented language, it can deal with non-OO entities as well.
Think, your main form is property of which form ?
The answer is that main form is not the property of ANY other form, it is a global object. Same exactly thing should be done about your function.
unit Main;
interface uses .......;
type TMainForm = class (TForm)....
....
end;
Procedure LogError(const message: string);
implementation uses .....;
Procedure LogError(const message: string);
begin
...
end;
(**********)
procedure TMainForm.FormCreate(...);
begin
....
end;
...
end.
Now even better option would be to totally decouple LogError from ANY form at all.
Unit MyLogger;
interface uses ....; // but does not uses Main or any other form
procedure SetupLog(....);
// to initialize it and change any settings you may wish
// maybe you would choose to save messages to file
// or use Windows debug interface - OutputDebugString
// or to a network SysLog server
// or just WriteLn it to a text window
// or to some TMemo in a graphic window
// or to file AND to a memo - that also might make sense.
// just keep it extensible
Procedure LogWarning(const message: string);
Procedure LogError(const message: string);
implementation uses ...;
function GenericLoggingSink(.....);
begin
...
end;
Procedure LogError(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Error, message, ... );
end;
Procedure LogWarning(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Warning, message, ... );
end;
And your Main form should just use this unit and this function on the same terms as all other forms in your application.
As part three I suggest you think what you want from your logging procedures.
Actually doing this only makes sense for very simplistic logging needs. Very simplistic.
Otherwise just take any existing Delphi logging frameworks and utilize a lot of functionality implemented there. For example can you just log an object of any class you would write?
The only reason to make your own logging library is "my program is so simplistic, that it does need only the most primitive logging. It would be faster to improvise my own system, than to copy-paste library initialization and setup from examples to a ready-made libraries".
Log4Delphi and Log4D are well-known FLOSS libraries, though they look somewhat abandoned. Maybe there just is nothing left to be extended. Those are most old ones, but there also are some newer FLOSS libraries, an easy example being http://blog.synopse.info/post/2013/02/03/Log-to-the-console
If anything, you can read their texts and learn from them.
There are also commercial libraries like those listed at Is there a way to do normal logging with EureakLog? and Delphi itself is shipped with a limited version of CodeSite logging framework.
Delphi IDE enhancements like CnWizards and GExperts also do come with a simplified debug-oriented logging interface.
Google would bring you even more options if you'd dare :-)

Avoid handling EAssertionFailed

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.

Scope of Application.OnException AND Delphi

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.

Exceptions on DBGrid editing

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.

How not to show system messages - Delphi

Consider this part of code:
Try
arqTXT.LoadFromFile(LogPath);
finally
ShowMessage(" The log file could not be found. Check if the service is running. ");
end;
After my message, a system message appears with the message: Cannot open file "C:\log.txt". Is there a way to not show this system message?
I'm using Delphi 2010
Thanks.
You would like to trap that specific exception (EInOutError if I remember correctly) and let other exceptions that occurred to remain unmuted. So if you get some other exception (e.g. EOutOfMemory) if wont get "swallowed" leaving you with seemingly working but broken code.
try
..
except
on E: EInOutError do
begin
..
end;
end;
You can have many exceptions handlers in one construct. Any unhandled exceptions will continue to propagate outside of the try .. except block, until caught elsewhere (e.g. by default exception handler that shows the error box).
There's a couple of things to point out. As other answers/comments explain, you should be wrapping your code inside of a try..except block instead of a try..finally block. I'm not understanding how your code example is supposed to work, because as it's currently written, your message will always show, whether there was an exception or not. It should look something more like...
try
arqTXT.LoadFromFile(LogPath);
except
on E: EInOutError do begin
ShowMessage('The log file could not be found. Check if the service is running. Message: '
+ E.Message);
end
end;
That code will catch and handle I/O exceptions. You would use a different exception type instead of EInOutError to handle different types of exceptions, or the dirty way is to use on E: Exception to catch all types. You can also identify the specific error code using GetLastError and further recognize / log this error code to know exactly what's wrong (File missing, read-only, etc.), assuming that an I/O exception has occured.
Here's a good article explaining how to handle exceptions in Delphi.
The other thing to point out is when you see the exceptions. When you're in Debug mode (running application from the IDE), it will show all exceptions by default, even the ones that you don't see when your application runs on its own. You can disable this in the IDE. That article is for Delphi 2007, but I'm pretty sure it should apply for 2010 as well, because the same options are in Delphi XE2.
Solve it using
try
exception
on e: exception do
ShowMessage("My Message");
Instead of Try/Finally.

Resources