Advertisement
RandomClear

How to create custom error dialog

Nov 25th, 2013
702
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Delphi 9.46 KB | None | 0 0
  1. // See also: https://pastebin.com/fs1CwVfZ - "how to use VCL form as exception dialog"
  2. // See also: https://pastebin.com/BFhcdsnh - "how to replace dialog icon"
  3. // See also: https://pastebin.com/S5h3VB3J - "how to subclass without changing class name"
  4.  
  5. // You may be not satisfied by standard EurekaLog dialogs, so you may want to use your own dialog.
  6. // Here is what you need to do: create class inheriting from abstract TBaseDialog class (EDialog unit), register it, and set ExceptionDialogType option.
  7. // The following sample shows 4 new dialog.
  8. //
  9. // As you can see, the central method here is ShowModalInternal. It does all the work.
  10. // It's abstract and must be overwritten in child classes.
  11. // We use this method to show MessageBox, but you can do something else.
  12. // Like creating and showing VCL or FMX form.
  13. //
  14. // All other methods of TBaseDialog are virtual, but not abstract. They contain default behavior.
  15. // You can override them to alter behavior, but you don't have to.
  16. // Base dialog class contains large number of helpers (methods and properties).
  17. // All that dialog needs to do is to invoke these methods in right order.
  18. // Therefore any child class can use powerful tools to quickly build new dialog.
  19. // Note: there is another abstract dialog class - TWinAPIDialog from EDialogWinAPI unit.
  20. // It's useful if you want to create new dialog based on direct WinAPI calls,
  21. // rather than using ready functions or frameworks (VCL/CLX/FMX).
  22. //
  23. // Important note: dialog class is responsible for almost whole exception processing.
  24. // That's because "dialog" don't have to be visual.
  25. // Think about Win32 service, system log, WER (Windows Error Reporting), etc.
  26. // So, this is not always possible to distinguish between "error dialog" and "exception processing".
  27. // That's why these concepts are both controlled by single "dialog" class.
  28. // As we saw above, a major method for visual dialog is ShowModalInternal method.
  29. // But real entry point is Execute method.
  30. // You can see its default implementation in TBaseDialog.Execute.
  31.  
  32. uses
  33.   EDialog, EClasses, ETypes;
  34.  
  35. type
  36.   // "Empty" dialog that does nothing at all
  37.   TNullDialog = class(TBaseDialog)
  38.   protected
  39.     procedure Beep; override;
  40.     function ShowModalInternal: TResponse; override;
  41.   public
  42.     class function ThreadSafe: Boolean; override;
  43.   end;
  44.  
  45.   // MessageBox dialog
  46.   TMessageBoxDialog = class(TBaseDialog)
  47.   protected
  48.     function ShowModalInternal: TResponse; override;
  49.     procedure Beep; override;
  50.   public
  51.     class function ThreadSafe: Boolean; override;
  52.   end;
  53.  
  54.   // A variant of MessageBox with more detailed message (with call stack)
  55.   TMessageBoxDetailedDialog = class(TMessageBoxDialog)
  56.   protected
  57.     function ExceptionMessage: String; override;
  58.   end;
  59.  
  60.   // "Default" dialog - dialog that invokes standard dialog (non-EurekaLog)
  61.   TRTLHandlerDialog = class(TBaseDialog)
  62.   protected
  63.     procedure Beep; override;
  64.     function GetCallRTLExceptionEvent: Boolean; override;
  65.     function ShowModalInternal: TResponse; override;
  66.   end;
  67.  
  68. { TNullDialog }
  69.  
  70. procedure TNullDialog.Beep;
  71. begin
  72.   // does nothing - no beep needed
  73. end;
  74.  
  75. // Main method: do nothing, return success
  76. function TNullDialog.ShowModalInternal: TResponse;
  77. begin
  78.   SetReproduceText(ReproduceText);
  79.  
  80.   Result.SendResult := srSent;
  81.   Result.ErrorCode := ERROR_SUCCESS;
  82.   Result.ErrorMessage := '';
  83. end;
  84.  
  85. // Indicate that we can be called from any thread
  86. // (this should be False for VCL/CLX/FMX dialogs)
  87. class function TNullDialog.ThreadSafe: Boolean;
  88. begin
  89.   Result := True;
  90. end;
  91.  
  92. { TMessageBoxDialog }
  93.  
  94. procedure TMessageBoxDialog.Beep;
  95. begin
  96.   // does nothing - beep is invoked by Windows.MessageBox in
  97.   // TMessageBoxDialog.ShowModalInternal
  98. end;
  99.  
  100. // Main method
  101. function TMessageBoxDialog.ShowModalInternal: TResponse;
  102. var
  103.   Flags: Cardinal;
  104.   Msg: String;
  105. begin
  106.   // Set default result
  107.   Result.ErrorCode := ERROR_SUCCESS;
  108.   Result.ErrorMessage := '';
  109.   if SendErrorReportChecked then
  110.     Result.SendResult := srSent
  111.   else
  112.     Result.SendResult := srCancelled;
  113.  
  114.   // Prepare message to show
  115.   Msg := ExceptionMessage;
  116.   if ShowSendErrorControl then
  117.   begin
  118.     Msg := Format(Options.CustomizedExpandedTexts[mtSend_AskSend], [Msg]);
  119.     Flags := MB_YESNO;
  120.   end
  121.   else
  122.     Flags := MB_OK;
  123.   Flags := Flags or MB_ICONERROR or MB_TASKMODAL;
  124.   if SendErrorReportChecked or (not ShowSendErrorControl) then
  125.     Flags := Flags or MB_DEFBUTTON1
  126.   else
  127.     Flags := Flags or MB_DEFBUTTON2;
  128.  
  129.   // Call actual MessageBox and set result
  130.   case MessageBox(Msg,
  131.                   Options.CustomizedExpandedTexts[mtDialog_Caption],
  132.                   Flags) of
  133.     0: Result.ErrorCode := GetLastError;
  134.     IDYes:
  135.        Result.SendResult := srSent;
  136.     IDNo:
  137.        Result.SendResult := srCancelled;
  138.   end;
  139.  
  140.   // Save error code/error message for failures
  141.   if Result.ErrorCode <> ERROR_SUCCESS then
  142.   begin
  143.     Result.SendResult := srUnknownError;
  144.     Result.ErrorMessage := SysErrorMessage(Result.ErrorCode);
  145.   end
  146.   else
  147.     SetReproduceText(ReproduceText);
  148. end;
  149.  
  150. // Can be called from any thread
  151. class function TMessageBoxDialog.ThreadSafe: Boolean;
  152. begin
  153.   Result := True;
  154. end;
  155.  
  156. { TRTLHandlerDialog }
  157.  
  158. // Indicate desire to invoke RTL handler
  159. function TRTLHandlerDialog.GetCallRTLExceptionEvent: Boolean;
  160. begin
  161.   Result := True;
  162. end;
  163.  
  164. function TRTLHandlerDialog.ShowModalInternal: TResponse;
  165. begin
  166.   SetReproduceText(ReproduceText);
  167.  
  168.   Result.SendResult := srRestart;  // means "call RTL handler"
  169.   Result.ErrorCode := ERROR_SUCCESS;
  170.   Result.ErrorMessage := '';
  171. end;
  172.  
  173. procedure TRTLHandlerDialog.Beep;
  174. begin
  175.   // Does nothing - transfer work to RTL handler
  176. end;
  177.  
  178. { TMessageBoxDetailedDialog }
  179.  
  180. // This one is a bit more complex - we want to add call stack to error message.
  181. // However, default form is not very readable with variable-width fonts.
  182. // That's why first we need a way to format call stack in another way.
  183.  
  184. type
  185.   // Our new formatter
  186.   TMessageBoxDetailedFormatter = class(TEurekaBaseStackFormatter)
  187.   protected
  188.     function GetItemText(const AIndex: Integer): String; override;
  189.     function GetStrings: TStrings; override;
  190.   end;
  191.  
  192. // Forms one line of call stack
  193. function TMessageBoxDetailedFormatter.GetItemText(const AIndex: Integer): String;
  194. var
  195.   Cache: TEurekaDebugInfo;
  196.   Info: PEurekaDebugInfo;
  197.   ModuleName, UnitName, RoutineName, LineInfo: String;
  198. begin
  199.   Info := CallStack.GetItem(AIndex, Cache);
  200.  
  201.   ModuleName := ExtractFileName(Info^.Location.ModuleName);
  202.   UnitName := Info^.Location.UnitName;
  203.  
  204.   if UnitName = ChangeFileExt(ModuleName, '') then
  205.     UnitName := ''
  206.   else
  207.     UnitName := '.' + UnitName;
  208.  
  209.   RoutineName := CallStack.ComposeName
  210.     (Info^.Location.ClassName, Info^.Location.ProcedureName);
  211.   if RoutineName <> '' then
  212.     RoutineName := '.' + RoutineName;
  213.  
  214.   if Info^.Location.LineNumber > 0 then
  215.     LineInfo := Format(',%d[%d]',
  216.       [Info^.Location.LineNumber, Info^.Location.ProcOffsetLine])
  217.   else
  218.     LineInfo := '';
  219.  
  220.   Result := ModuleName + UnitName + RoutineName + LineInfo;
  221. end;
  222.  
  223. // Formats entire call stack
  224. function TMessageBoxDetailedFormatter.GetStrings: TStrings;
  225. var
  226.   ThreadID: Cardinal;
  227.   I: Integer;
  228.   Line: String;
  229.   Stack: TEurekaBaseStackList;
  230. begin
  231.   if not Assigned(FStr) then
  232.   begin
  233.     FStr := TStringList.Create;
  234.     FModified := True;
  235.   end;
  236.   if FModified then
  237.   begin
  238.     Stack := CallStack;
  239.     CalculateLengths;
  240.     FStr.BeginUpdate;
  241.     try
  242.       FStr.Clear;
  243.       FStr.Capacity := Stack.Count;
  244.  
  245.       if Stack.Count > 0 then
  246.       begin
  247.         ThreadID := Stack.Items[0].ThreadID;
  248.         for I := 0 to Stack.Count - 1 do
  249.         begin
  250.           if (Stack.Items[I].Location.Module <> 0) and
  251.              (Stack.Items[I].Location.DebugDetail in [ddUnit..ddSourceCode]) and
  252.              (Stack.Items[I].ThreadID = ThreadID) then
  253.           begin
  254.             Line := GetItemText(I);
  255.             if (FStr.Count <= 0) or (FStr[FStr.Count - 1] <> Line) then
  256.               FStr.Add(Line);
  257.           end;
  258.         end;
  259.       end;
  260.     finally
  261.       FStr.EndUpdate;
  262.     end;
  263.     FModified := False;
  264.   end;
  265.   Result := FStr;
  266. end;
  267.  
  268. // Append call stack to error message
  269. function TMessageBoxDetailedDialog.ExceptionMessage: String;
  270. const
  271.   MaxLines = 15;
  272. var
  273.   Formatter: TMessageBoxDetailedFormatter;
  274.   Stack: TEurekaBaseStackList;
  275. begin
  276.   {$WARNINGS OFF}
  277.   // Abstract methods are intended here.
  278.   // It is like assert: they should not be called.
  279.   Formatter := TMessageBoxDetailedFormatter.Create;
  280.   {$WARNINGS ON}
  281.   try
  282.  
  283.     if Assigned(CallStack) then
  284.       Formatter.Assign(CallStack.Formatter);
  285.     Formatter.CaptionHeader := '';
  286.  
  287.     Stack := nil;
  288.     try
  289.       if CallStack <> nil then
  290.       begin
  291.         Stack := TEurekaStackList.Create;
  292.         Stack.Assign(CallStack);
  293.         while Stack.Count > MaxLines do
  294.           Stack.Delete(Stack.Count - 1);
  295.       end;
  296.       Result := inherited ExceptionMessage + sLineBreak + sLineBreak +
  297.                 CallStackToString(Stack, '', Formatter);
  298.     finally
  299.       FreeAndNil(Stack);
  300.     end;
  301.   finally
  302.     FreeAndNil(Formatter);
  303.   end;
  304. end;
  305.  
  306. ...
  307.  
  308. initialization
  309.  
  310.   RegisterDialogClass(TNullDialog);
  311.   RegisterDialogClass(TMessageBoxDialog);
  312.   RegisterDialogClass(TMessageBoxDetailedDialog);
  313.   RegisterDialogClass(TRTLHandlerDialog);
  314.  
  315. end.
  316.  
  317. // Usage:
  318. CurrentEurekaModuleOptions.ExceptionDialogType := TMessageBoxDetailedDialog.ClassName;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement