If you just want to alter visual appearance or behavior of build-in dialog (such as MS Classic, EurekaLog, etc.) - please, see this article instead.
Note for EurekaLog 6 users
You can implement EurekaLog 7 exception dialog in the same style as in EurekaLog 6: by utilizing event handler. Just add EEvents unit to uses, register your own OnExceptionNotify handler, and show your dialog (see: How to register event handler).
This, however, is not recommended approach, as you won't get access to any of already written dialog code and won't be able to use many helper routines.
The plus side is that your old code from EurekaLog 6 will remain mostly unmodified. However, best approach would be to implement dialog in EurekaLog 7 style - as described below.
If you are going to use EurekaLog 6 style (e.g. call dialog from event handlers) and you are going to use VCL - you have to add synchronization. VCL is not thread safe, and EurekaLog 7 event handlers are called by the thread that has raised the exception. This is different from EurekaLog 6. EurekaLog 6 called all events from main thread.
Base dialog class is TBaseDialogClass from EDialog unit. All dialogs must be child classes from this class. If you want to create your own dialog - you need to create child class from any dialog class and override desired virtual methods. You must override all abstract virtual methods at very least. You can also call inherited helper methods to simplify developing.
Important Note: dialog class is responsible for almost whole exception processing. That's because "dialog" don't have to be visual. Think about Win32 service, system log, WER (Windows Error Reporting), etc. So, this is not always possible to distinguish between "error dialog" and "exception processing". That's why these concepts are both controlled by single "dialog" class. A primary method for visual dialog is ShowModalInternal method. But real entry point for dialog is Execute method, which performs all tasks: saving bug report, showing dialog, sending, etc.
First, create your own dialog class:
uses
EDialog, // for TBaseDialog and RegisterDialogClass
EModules; // for CurrentEurekaLogOptions
type
// Your exception dialog
TMyExceptionDialog = class(TBaseDialog)
// ...
Second, register your dialog class:
initialization
// Register your dialog, so it can be used by EurekaLog
RegisterDialogClass(TMyExceptionDialog);
end.
Third, switch dialog settings to use your dialog:
initialization
// ...
// Switch to your dialog
CurrentEurekaLogOptions.ExceptionDialogType := TMyExceptionDialog.ClassName;
end.
Note: you can move chaging CurrentEurekaLogOptions.ExceptionDialogType somewhere else.
Now that preparations are completed - you can implement your dialog in your dialog class. You must implement ShowModalInternal abstract method at very least. For example, if you want to use a VCL form as exception dialog:
uses
EDialog, // for TBaseDialog and RegisterDialogClass
EModules, // for CurrentEurekaLogOptions
EException, // for TEurekaExceptionInfo
EClasses, // for TEurekaModuleOptions
ETypes; // for TResponse and other simple EurekaLog types
type
// Your exception dialog
TMyExceptionDialog = class(TBaseDialog)
protected
function ShowModalInternal: TResponse; override;
procedure Beep; override;
public
class function ThreadSafe: Boolean; override;
end;
// VCL form for your exception dialog
TMyExceptionDialogForm = class(TForm)
// ...
private
FDialog: TMyExceptionDialog;
FExceptionInfo: TEurekaExceptionInfo;
FOptions: TEurekaModuleOptions;
protected
property Dialog: TMyExceptionDialog read FDialog;
property ExceptionInfo: TEurekaExceptionInfo read FExceptionInfo;
property Options: TEurekaModuleOptions read FOptions;
public
constructor Create(const ADialog: TMyExceptionDialog); reintroduce;
end;
{ TMyExceptionDialog }
function TMyExceptionDialog.ShowModalInternal: TResponse;
var
fmExceptionDialogForm: TMyExceptionDialogForm;
begin
try
// Create form and setup form
fmExceptionDialogForm := TMyExceptionDialogForm.Create(Self { <- important } );
try
// Show form
fmExceptionDialogForm.ShowModal;
// Return default result
// See below for more examples
Finalize(Result);
FillChar(Result, SizeOf(Result), 0);
Result.SendResult := srSent;
finally
FreeAndNil(fmExceptionDialogForm);
end;
except
on E: Exception do
begin
// Indicate that dialog failed:
Finalize(Result);
FillChar(Result, SizeOf(Result), 0);
Result.SendResult := srUnknownError;
if E is EOSError then
Result.ErrorCode := EOSError(E).ErrorCode
else
Result.ErrorCode := ERROR_GEN_FAILURE;
Result.ErrorMessage := E.Message;
end;
end;
end;
procedure TMyExceptionDialog.Beep;
begin
// This is just an example
// This is a default behavior
MessageBeep(MB_ICONERROR);
end;
class function TMyExceptionDialog.ThreadSafe: Boolean;
begin
Result := False; // VCL is not thread safe, indicate this
end;
{ TMyExceptionDialogForm }
constructor TMyExceptionDialogForm.Create(const ADialog: TMyExceptionDialog);
begin
FDialog := ADialog;
FExceptionInfo := FDialog.ExceptionInfo;
FOptions := FDialog.Options;
inherited Create(nil);
// Here: you can customize your form
// using .ExceptionInfo and .Options properties
Caption := ExceptionInfo.ExceptionClass;
Label1.Caption := ExceptionInfo.ExceptionMessage;
// ...
end;
Important Note: We consider using VCL/FMX forms as exception dialogs to be a bad practice for the following reasons:
• | VCL is not thread safe. You won't be able to show exception dialog for each background thread. Exception info must be send back to main thread in order to show dialog. |
• | VCL is a complex library. If you get some exception which damages VCL - then you won't be able to show exception dialog built with VCL. |
For the above reason, EurekaLog does not use VCL or FMX, but implements exception dialogs with naked WinAPI. Consider yourself warned. If you want to implement your custom new dialog as WinAPI-dialog - then you need to create a child class from TWinAPIDialog class (EDialogWinAPI unit). See this article for an example.
Here are some examples of what you can do with dialog class:
(note that you can use ModalResult of your form to select what ShowModalInternal should return)
function TMyExceptionDialog.ShowModalInternal: TResponse;
begin
// ...
// Set result, which means "all is OK, send bug report (if that is set in options)"
Finalize(Result);
FillChar(Result, SizeOf(Result), 0);
Result.SendResult := srSent;
end;
function TMyExceptionDialog.ShowModalInternal: TResponse;
begin
// ...
// Set result, which means "all is OK, but do not send bug report"
Finalize(Result);
FillChar(Result, SizeOf(Result), 0);
Result.SendResult := srCancelled;
end;
function TMyExceptionDialog.ShowModalInternal: TResponse;
begin
// ...
RestartApplication; // <- to restart application immediately
// TerminateApplication; // <- to terminate application immediately
// SetTerminateApplication(True); // <- you can use this in checkbox on the form
// for delayed termination on exit
Finalize(Result);
FillChar(Result, SizeOf(Result), 0);
Result.SendResult := srCancelled;
end;
function TMyExceptionDialog.ShowModalInternal: TResponse;
begin
// ...
// Fills FResponse with values telling "Switch to EurekaLog detailed dialog"
ShowDetails;
Result := FResponse;
end;
function TMyExceptionDialog.ShowModalInternal: TResponse;
begin
// ...
// Same, but show "Provide reproduce steps" dialog instead
ShowAskReproduce;
Result := FResponse;
// Alternatively, if you are going to ask reproduce steps yourself:
// SetReproduceText(fmExceptionDialogForm.ReproduceStepsMemo.Text);
end;
function TMyExceptionDialog.ShowModalInternal: TResponse;
begin
// ...
// Will switch back to RTL (non-EurekaLog) default dialog
// (e.g. like MessageBox for VCL visual apps)
// This is useful for non-visual dialogs
ShowRTL;
Result := FResponse;
end;
procedure TMyExceptionDialogForm.URLLabelClick(Sender: TObject);
begin
// Open your web-site and (optionally) supply exception's BugID
ShellExec(Format('http://www.example.com/feedback.php?BugID=%s', [ExceptionInfo.BugIDStr]));
end;
procedure TMyExceptionDialogForm.CopyButtonClick(Sender: TObject);
begin
// Copy full bug report into clipboard
// (in 2 forms: one as simple text, other is as file)
Dialog.CopyReportToClipboard;
// Copy call stack only:
// Clipboard.AsText := ExceptionInfo.CallStack.ToString;
end;
procedure TMyExceptionDialogForm.FormCreate(Sender: TObject);
var
I, C: Integer;
Error: Exception;
ExceptionThreadID: Cardinal;
begin
// Get exception object - in case you want to use it (not used in this example, though)
if Assigned(ExceptionInfo.ExceptionObject) and ExceptionInfo.ExceptionNative then
Error := Exception(ExceptionInfo.ExceptionObject)
else
// Will be nil for, say, ANSI exceptions from DLL caught in UNICODE exe or
// for inner exceptions, which were not chained officially via .RaiseOuterException method
Error := nil;
ListBox.Clear;
// Add some exception information:
ListBox.Items.Add(FmtPointerToStr(ExceptionInfo.Address));
ListBox.Items.Add(ExceptionInfo.ClassName);
ListBox.Items.Add(ExceptionInfo.ExceptionMessage);
// Add at most 5 items with line numbers from call stack
C := 0;
ExceptionThreadID := ExceptionInfo.CallStack[0].ThreadID;
for I := 0 to ExceptionInfo.CallStack.Count - 1 do
if (ExceptionInfo.CallStack[I].ThreadID = ExceptionThreadID) and
(ExceptionInfo.CallStack[I].Location.LineNumber > 0) then
begin
ListBox.Items.Add(LocationToStr(ExceptionInfo.CallStack[I].Location, False, False, False, False, False, True));
Inc(C);
if C > 5 then
Break;
end;
// Add some system information from bug report
ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_OSType], GetOSTypeStr]));
ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_OSBuildN], GetOSBuild]));
ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_OSUpdate], GetOSUpdate]));
ListBox.Items.Add(Format('%s: %s (%s)', [Options.CustomizedExpandedTexts[mtLog_OSLanguage], GetOSNonUnicodeLanguage, GetOSCharset]));
ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_CmpTotalMemory], FmtSize(GetTotalMemory)]));
ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_CmpFreeMemory], FmtSize(GetFreeMemory)]));
ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_CmpTotalDisk], FmtSize(GetTotalDisk)]));
// Add some custom information
ListBox.Items.Add('Application License: ' + {$IFDEF ENTERPRISE}'ENT'{$ELSE}'STD'{$ENDIF});
end;
If you want to insert user's input from your controls into bug report - see this article (scroll down to "Adding user input from custom dialog" section).
See also:
Send feedback...
|
Build date: 2024-09-30
Last edited: 2023-06-28
|
PRIVACY STATEMENT
The documentation team uses the feedback submitted to improve the EurekaLog documentation.
We do not use your e-mail address for any other purpose.
We will remove your e-mail address from our system after the issue you are reporting has been resolved.
While we are working to resolve this issue, we may send you an e-mail message to request more information about your feedback.
After the issues have been addressed, we may send you an email message to let you know that your feedback has been addressed.
Permanent link to this article: https://www.eurekalog.com/help/eurekalog/how_to_add_your_own_dialog.php
|
|