Root > How to... > ...change exception dialog? > ...add your own dialog?

...add your own dialog?

Previous pageReturn to chapter overviewNext page   

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: 2023-09-11
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