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.

 

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
  TMyExeptionDialog = class(TBaseDialog)
  // ...

 

Second, register your dialog class:

 

initialization
  // Register your dialog, so it can be used by EurekaLog
  RegisterDialogClass(TMyExeptionDialog);
end.

 

Third, switch dialog settings to use your dialog:

 

initialization
  // ...
 
  // Switch to your dialog
  CurrentEurekaLogOptions.ExceptionDialogType := TMyExeptionDialog.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
  TMyExeptionDialog = class(TBaseDialog)
  protected
    function ShowModalInternal: TResponse; override;
    procedure Beep; override;

  public
    class function ThreadSafe: Boolean; override;
  end;
 
  // VCL form for your exception dialog
  TMyExeptionDialogForm = class(TForm)
    // ...
  private
    FDialog: TMyExeptionDialog;
    FExceptionInfo: TEurekaExceptionInfo;
    FOptions: TEurekaModuleOptions;
  protected
    property Dialog: TMyExeptionDialog read FDialog;
    property ExceptionInfo: TEurekaExceptionInfo read FExceptionInfo;
    property Options: TEurekaModuleOptions read FOptions;
  public
    constructor Create(const ADialog: TMyExeptionDialog); reintroduce;
  end;
 
{ TMyExeptionDialog }
 
function TMyExeptionDialog.ShowModalInternal: TResponse;
var
  fmExceptionDialogForm: TMyExeptionDialogForm;
begin
  try
    // Create form and setup form
    fmExceptionDialogForm := TMyExeptionDialogForm.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 TMyExeptionDialog.Beep;
begin
  // This is just an example
  // This is a default behavior
  MessageBeep(MB_ICONERROR);
end;

 
class function TMyExeptionDialog.ThreadSafe: Boolean;
begin
  Result := False; // VCL is not thread safe, indicate this
end;
 
{ TMyExeptionDialogForm }
 
constructor TMyExeptionDialogForm.Create(const ADialog: TMyExeptionDialog);
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 TMyExeptionDialog.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 TMyExeptionDialog.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 TMyExeptionDialog.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 TMyExeptionDialog.ShowModalInternal: TResponse;
begin
  // ...
 
  // Fills FResponse with values telling "Switch to EurekaLog detailed dialog"
  ShowDetails;
  Result := FResponse;
end
 
function TMyExeptionDialog.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 TMyExeptionDialog.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 TMyExeptionDialogForm.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 TMyExeptionDialogForm.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 TMyExeptionDialogForm.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
    Error := nil// will be nil for, say, ANSI exceptions from DLL caught in UNICODE exe
 
  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: 2020-10-22
Last edited: 2020-05-20
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