Advertisement
RandomClear

How to use VCL form as exception dialog

Jul 13th, 2015
682
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Delphi 10.58 KB | None | 0 0
  1. // See also: https://pastebin.com/rvsPpWpf - "how to create custom error dialog"
  2. // See also: https://pastebin.com/BFhcdsnh - "how to replace dialog icon"
  3. // See also: https://pastebin.com/jrEcvkkv - "how to change format of bug report file"
  4. // See also: https://pastebin.com/46QkwZNi - "how to convert call stack to text"
  5.  
  6. // IMPORTANT NOTE
  7.  
  8. // We consider using VCL/FMX forms as exception dialogs to be bad practice for the following reasons:
  9. // - 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.
  10. // - 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.
  11. // For the above reason, EurekaLog does not use VCL or FMX, but implements exception dialogs with naked WinAPI.
  12. // Consider yourself warned.
  13.  
  14. // You can implement EurekaLog 7 exception dialog in the same style as in EurekaLog 6: by utilizing event handler.
  15. // Just add EEvents unit to uses, register your own OnExceptionNotify handler
  16. // ( http://www.eurekalog.com/help/eurekalog/topic_type_eevents_televexceptionnotifymeth.php ),
  17. // and show your form.
  18. // This, however, is not recommended approach,
  19. // as you won't get access to any of already written dialog code
  20. // and won't be able to use many helper routines.
  21. // But the plus side is that your old code from EurekaLog 6 will remain mostly unmodified.
  22. // Best approach would be to implement dialog in EurekaLog 7 style - by declaring your own dialog class:
  23.  
  24. unit UnitExceptionDialog;
  25.  
  26. interface
  27.  
  28. uses
  29.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  30.   Dialogs, StdCtrls,
  31.  
  32.   EDialog,    // for TBaseDialog and RegisterDialogClass
  33.   EException, // for TEurekaExceptionInfo
  34.   EClasses,   // for TEurekaModuleOptions
  35.   ETypes;     // for TResponse and other simple EurekaLog types
  36.  
  37. type
  38.   // My exception dialog
  39.   TMyExeptionDialog = class(TBaseDialog)
  40.   protected
  41.     function ShowModalInternal: TResponse; override;
  42.   public
  43.     class function ThreadSafe: Boolean; override;
  44.   end;
  45.  
  46.   // Form for my exception dialog
  47.   TMyExeptionDialogForm = class(TForm)
  48.     URLLabel: TLabel;
  49.     ListBox: TListBox;
  50.     CopyButton: TButton;
  51.     TryButton: TButton;
  52.     ExitButton: TButton;
  53.     SendAndTry: TButton;
  54.     procedure FormCreate(Sender: TObject);
  55.     procedure SendAndTryClick(Sender: TObject);
  56.     procedure TryButtonClick(Sender: TObject);
  57.     procedure ExitButtonClick(Sender: TObject);
  58.     procedure CopyButtonClick(Sender: TObject);
  59.     procedure URLLabelClick(Sender: TObject);
  60.     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  61.   private
  62.     FDialog: TMyExeptionDialog;
  63.     FExceptionInfo: TEurekaExceptionInfo;
  64.     FOptions: TEurekaModuleOptions;
  65.   protected
  66.     property Dialog: TMyExeptionDialog read FDialog;
  67.     property ExceptionInfo: TEurekaExceptionInfo read FExceptionInfo;
  68.     property Options: TEurekaModuleOptions read FOptions;
  69.   public
  70.     constructor Create(const ADialog: TMyExeptionDialog); reintroduce;
  71.   end;
  72.  
  73. implementation
  74.  
  75. uses
  76.   Clipbrd,
  77.  
  78.   ECore,       // for ShellExec
  79.   EDebugInfo,  // for LocationToStr
  80.   EInfoFormat, // for various FmtXYZ functions
  81.   ESysInfo,    // for various GetXYZ functions
  82.   EModules;    // for CurrentEurekaLogOptions
  83.  
  84. {$R *.dfm}
  85.  
  86. { TMyExeptionDialog }
  87.  
  88. function TMyExeptionDialog.ShowModalInternal: TResponse;
  89. var
  90.   fmExceptionDialogForm: TMyExeptionDialogForm;
  91. begin
  92.   try
  93.     // Create form and setup form
  94.     fmExceptionDialogForm := TMyExeptionDialogForm.Create(Self { <- important } );
  95.     try
  96.       // Show form
  97.       case fmExceptionDialogForm.ShowModal of
  98.         mrYes: // see TMyExeptionDialogForm.SendAndTryClick below
  99.         begin
  100.           // Set result, which means "all is OK, send bug report (if that is set in options)"
  101.           Finalize(Result);
  102.           FillChar(Result, SizeOf(Result), 0);
  103.           Result.SendResult := srSent;
  104.         end;
  105.         mrOK:  // see TMyExeptionDialogForm.TryClick below
  106.         begin
  107.           // Set result, which means "all is OK, but do not send bug report"
  108.           Finalize(Result);
  109.           FillChar(Result, SizeOf(Result), 0);
  110.           Result.SendResult := srCancelled;
  111.         end;
  112.         mrCancel: // see TMyExeptionDialogForm.ExitButtonClick below
  113.         begin
  114.           RestartApplication; // <- to restart application immediately
  115.           // TerminateApplication; // <- to terminate application immediately
  116.           // SetTerminateApplication(True); // <- you can use this in CheckBox.OnClick - for delayed termination on exit
  117.         end;
  118.       else
  119.         begin
  120.           // Set default result
  121.           Finalize(Result);
  122.           FillChar(Result, SizeOf(Result), 0);
  123.           Result.SendResult := srSent;
  124.         end;
  125.       end;
  126.  
  127.       // You may also try other options:
  128.       // Result.SendResult := srRestart; // <- "show me another dialog, please"
  129.       // Options.ExceptionDialogType := edtEurekaLogDetailed; // <- dialog to show (in this case: detailed EurekaLog dialog with bug report and call stack)
  130.       // Options.CustomField[difDetailsFallbackClass] := ClassName; // <- remember this dialog, if edtEurekaLogDetailed will revert back
  131.       // Options.CustomFieldInt[difOldSendResult] := Ord(srSent); // <- what is result for current dialog, srSent or srCancelled
  132.  
  133.       // The above could be replaced with simple:
  134.       // ShowDetails; // <- this will setup FResponse with the above code
  135.       // Result := FResponse;
  136.  
  137.       // You can also use the following:
  138.       // ShowAskReproduce;  // <- this will switch dialog to "ask steps to reproduce" dialog
  139.       // Result := FResponse;
  140.  
  141.     finally
  142.       FreeAndNil(fmExceptionDialogForm);
  143.     end;
  144.   except
  145.     on E: Exception do
  146.     begin
  147.       // Indicate that dialog failed:
  148.       Finalize(Result);
  149.       FillChar(Result, SizeOf(Result), 0);
  150.       Result.SendResult := srUnknownError;
  151.       if E is EOSError then
  152.         Result.ErrorCode := EOSError(E).ErrorCode
  153.       else
  154.         Result.ErrorCode := ERROR_GEN_FAILURE;
  155.       Result.ErrorMessage := E.Message;
  156.     end;
  157.   end;
  158. end;
  159.  
  160. class function TMyExeptionDialog.ThreadSafe: Boolean;
  161. begin
  162.   Result := False; // VCL is not thread safe, indicate this
  163. end;
  164.  
  165. { TMyExeptionDialogForm }
  166.  
  167. constructor TMyExeptionDialogForm.Create(const ADialog: TBaseDialog);
  168. begin
  169.   FDialog := ADialog;
  170.   FExceptionInfo := FDialog.ExceptionInfo;
  171.   FOptions := FDialog.Options;
  172.   inherited Create(nil);
  173. end;
  174.  
  175. procedure TMyExeptionDialogForm.FormCreate(Sender: TObject);
  176. var
  177.   I, C: Integer;
  178.   Error: Exception;
  179. begin
  180.   // Get exception object - in case you want to use it (not used in this example, though)
  181.   if Assigned(ExceptionInfo.ExceptionObject) and ExceptionInfo.ExceptionNative then
  182.     Error := Exception(ExceptionInfo.ExceptionObject)
  183.   else
  184.     Error := nil; // will be nil for, say, ANSI exceptions from DLL caught in UNICODE exe
  185.  
  186.   ListBox.Clear;
  187.  
  188.   // Add some exception information:
  189.   ListBox.Items.Add(FmtPointerToStr(ExceptionInfo.Address));
  190.   ListBox.Items.Add(ExceptionInfo.ClassName);
  191.   ListBox.Items.Add(ExceptionInfo.ExceptionMessage);
  192.  
  193.   // Add at most 5 items with line numbers from call stack
  194.   C := 0;
  195.   for I := 0 to ExceptionInfo.CallStack.Count - 1 do
  196.     if ExceptionInfo.CallStack[I].Location.LineNumber > 0 then
  197.     begin
  198.       ListBox.Items.Add(LocationToStr(ExceptionInfo.CallStack[I].Location, False, False, False, False, False, True));
  199.       Inc(C);
  200.       if C > 5 then
  201.         Break;
  202.     end;
  203.  
  204.   // Add some system information from bug report
  205.   ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_OSType], GetOSTypeStr]));
  206.   ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_OSBuildN], GetOSBuild]));
  207.   ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_OSUpdate], GetOSUpdate]));
  208.   ListBox.Items.Add(Format('%s: %s (%s)', [Options.CustomizedExpandedTexts[mtLog_OSLanguage], GetOSNonUnicodeLanguage, GetOSCharset]));
  209.   ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_CmpTotalMemory], FmtSize(GetTotalMemory)]));
  210.   ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_CmpFreeMemory], FmtSize(GetFreeMemory)]));
  211.   ListBox.Items.Add(Format('%s: %s', [Options.CustomizedExpandedTexts[mtLog_CmpTotalDisk], FmtSize(GetTotalDisk)]));
  212.  
  213.   // Add some custom information
  214.   ListBox.Items.Add('Application License: ' + {$IFDEF ENTERPRISE}'ENT'{$ELSE}'STD'{$ENDIF});
  215. end;
  216.  
  217. procedure TMyExeptionDialogForm.URLLabelClick(Sender: TObject);
  218. begin
  219.   // Open your web-site and (optionally) supply exception's BugID
  220.   ShellExec(Format('http://www.example.com/feedback.php?BugID=%s', [ExceptionInfo.BugIDStr]));
  221. end;
  222.  
  223. procedure TMyExeptionDialogForm.SendAndTryClick(Sender: TObject);
  224. begin
  225.   ModalResult := mrYes; // any value, which you want to analyze in TMyExeptionDialog.ShowModalInternal
  226.   Hide;
  227. end;
  228.  
  229. procedure TMyExeptionDialogForm.TryButtonClick(Sender: TObject);
  230. begin
  231.   ModalResult := mrOk; // any value, which you want to analyze in TMyExeptionDialog.ShowModalInternal
  232.   Hide;
  233. end;
  234.  
  235. procedure TMyExeptionDialogForm.ExitButtonClick(Sender: TObject);
  236. begin
  237.   TerminateProcess(GetCurrentProcess, ExceptionInfo.ExceptionCode);
  238.   // Alternatively, you may try (see also above):
  239.   // ModalResult := mrCancel; // any value, which you want to analyze in TMyExeptionDialog.ShowModalInternal
  240.   // Hide;
  241. end;
  242.  
  243. procedure TMyExeptionDialogForm.CopyButtonClick(Sender: TObject);
  244. begin
  245.   Clipboard.AsText := ListBox.Items.Text;
  246.   // You may also try:
  247.   // Dialog.CopyReportToClipboard; // <- copies full bug report into clipboard (in 2 forms: one as simple text, other is as file)
  248.   // Clipboard.AsText := ExceptionInfo.CallStack.ToString; // <- copies call stack only
  249. end;
  250.  
  251. procedure TMyExeptionDialogForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  252. begin
  253.   // Don't forget to set the Form's KeyPreview property to true, or this won't trap the key
  254.   if Key = VK_F1 then
  255.     Application.HelpSystem.ShowTopicHelp('RefInternalError', Application.CurrentHelpFile);
  256. end;
  257.  
  258. initialization
  259.   // Register your dialog, so it can be used by EurekaLog
  260.   RegisterDialogClass(TMyExeptionDialog);
  261.   // Switch to your dialog
  262.   CurrentEurekaLogOptions.ExceptionDialogType := TMyExeptionDialog.ClassName;
  263.  
  264.   // You may keep RegisterDialogClass in this unit,
  265.   // but move chaging CurrentEurekaLogOptions.ExceptionDialogType somewhere else
  266.  
  267. end.
  268.  
  269. // Please note that dialog class have TONS of useful methods. Call them, don't be shy.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement