Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // See also: https://pastebin.com/fs1CwVfZ - "how to use VCL form as exception dialog"
- // See also: https://pastebin.com/BFhcdsnh - "how to replace dialog icon"
- // See also: https://pastebin.com/S5h3VB3J - "how to subclass without changing class name"
- // You may be not satisfied by standard EurekaLog dialogs, so you may want to use your own dialog.
- // Here is what you need to do: create class inheriting from abstract TBaseDialog class (EDialog unit), register it, and set ExceptionDialogType option.
- // The following sample shows 4 new dialog.
- //
- // As you can see, the central method here is ShowModalInternal. It does all the work.
- // It's abstract and must be overwritten in child classes.
- // We use this method to show MessageBox, but you can do something else.
- // Like creating and showing VCL or FMX form.
- //
- // All other methods of TBaseDialog are virtual, but not abstract. They contain default behavior.
- // You can override them to alter behavior, but you don't have to.
- // Base dialog class contains large number of helpers (methods and properties).
- // All that dialog needs to do is to invoke these methods in right order.
- // Therefore any child class can use powerful tools to quickly build new dialog.
- // Note: there is another abstract dialog class - TWinAPIDialog from EDialogWinAPI unit.
- // It's useful if you want to create new dialog based on direct WinAPI calls,
- // rather than using ready functions or frameworks (VCL/CLX/FMX).
- //
- // 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.
- // As we saw above, a major method for visual dialog is ShowModalInternal method.
- // But real entry point is Execute method.
- // You can see its default implementation in TBaseDialog.Execute.
- uses
- EDialog, EClasses, ETypes;
- type
- // "Empty" dialog that does nothing at all
- TNullDialog = class(TBaseDialog)
- protected
- procedure Beep; override;
- function ShowModalInternal: TResponse; override;
- public
- class function ThreadSafe: Boolean; override;
- end;
- // MessageBox dialog
- TMessageBoxDialog = class(TBaseDialog)
- protected
- function ShowModalInternal: TResponse; override;
- procedure Beep; override;
- public
- class function ThreadSafe: Boolean; override;
- end;
- // A variant of MessageBox with more detailed message (with call stack)
- TMessageBoxDetailedDialog = class(TMessageBoxDialog)
- protected
- function ExceptionMessage: String; override;
- end;
- // "Default" dialog - dialog that invokes standard dialog (non-EurekaLog)
- TRTLHandlerDialog = class(TBaseDialog)
- protected
- procedure Beep; override;
- function GetCallRTLExceptionEvent: Boolean; override;
- function ShowModalInternal: TResponse; override;
- end;
- { TNullDialog }
- procedure TNullDialog.Beep;
- begin
- // does nothing - no beep needed
- end;
- // Main method: do nothing, return success
- function TNullDialog.ShowModalInternal: TResponse;
- begin
- SetReproduceText(ReproduceText);
- Result.SendResult := srSent;
- Result.ErrorCode := ERROR_SUCCESS;
- Result.ErrorMessage := '';
- end;
- // Indicate that we can be called from any thread
- // (this should be False for VCL/CLX/FMX dialogs)
- class function TNullDialog.ThreadSafe: Boolean;
- begin
- Result := True;
- end;
- { TMessageBoxDialog }
- procedure TMessageBoxDialog.Beep;
- begin
- // does nothing - beep is invoked by Windows.MessageBox in
- // TMessageBoxDialog.ShowModalInternal
- end;
- // Main method
- function TMessageBoxDialog.ShowModalInternal: TResponse;
- var
- Flags: Cardinal;
- Msg: String;
- begin
- // Set default result
- Result.ErrorCode := ERROR_SUCCESS;
- Result.ErrorMessage := '';
- if SendErrorReportChecked then
- Result.SendResult := srSent
- else
- Result.SendResult := srCancelled;
- // Prepare message to show
- Msg := ExceptionMessage;
- if ShowSendErrorControl then
- begin
- Msg := Format(Options.CustomizedExpandedTexts[mtSend_AskSend], [Msg]);
- Flags := MB_YESNO;
- end
- else
- Flags := MB_OK;
- Flags := Flags or MB_ICONERROR or MB_TASKMODAL;
- if SendErrorReportChecked or (not ShowSendErrorControl) then
- Flags := Flags or MB_DEFBUTTON1
- else
- Flags := Flags or MB_DEFBUTTON2;
- // Call actual MessageBox and set result
- case MessageBox(Msg,
- Options.CustomizedExpandedTexts[mtDialog_Caption],
- Flags) of
- 0: Result.ErrorCode := GetLastError;
- IDYes:
- Result.SendResult := srSent;
- IDNo:
- Result.SendResult := srCancelled;
- end;
- // Save error code/error message for failures
- if Result.ErrorCode <> ERROR_SUCCESS then
- begin
- Result.SendResult := srUnknownError;
- Result.ErrorMessage := SysErrorMessage(Result.ErrorCode);
- end
- else
- SetReproduceText(ReproduceText);
- end;
- // Can be called from any thread
- class function TMessageBoxDialog.ThreadSafe: Boolean;
- begin
- Result := True;
- end;
- { TRTLHandlerDialog }
- // Indicate desire to invoke RTL handler
- function TRTLHandlerDialog.GetCallRTLExceptionEvent: Boolean;
- begin
- Result := True;
- end;
- function TRTLHandlerDialog.ShowModalInternal: TResponse;
- begin
- SetReproduceText(ReproduceText);
- Result.SendResult := srRestart; // means "call RTL handler"
- Result.ErrorCode := ERROR_SUCCESS;
- Result.ErrorMessage := '';
- end;
- procedure TRTLHandlerDialog.Beep;
- begin
- // Does nothing - transfer work to RTL handler
- end;
- { TMessageBoxDetailedDialog }
- // This one is a bit more complex - we want to add call stack to error message.
- // However, default form is not very readable with variable-width fonts.
- // That's why first we need a way to format call stack in another way.
- type
- // Our new formatter
- TMessageBoxDetailedFormatter = class(TEurekaBaseStackFormatter)
- protected
- function GetItemText(const AIndex: Integer): String; override;
- function GetStrings: TStrings; override;
- end;
- // Forms one line of call stack
- function TMessageBoxDetailedFormatter.GetItemText(const AIndex: Integer): String;
- var
- Cache: TEurekaDebugInfo;
- Info: PEurekaDebugInfo;
- ModuleName, UnitName, RoutineName, LineInfo: String;
- begin
- Info := CallStack.GetItem(AIndex, Cache);
- ModuleName := ExtractFileName(Info^.Location.ModuleName);
- UnitName := Info^.Location.UnitName;
- if UnitName = ChangeFileExt(ModuleName, '') then
- UnitName := ''
- else
- UnitName := '.' + UnitName;
- RoutineName := CallStack.ComposeName
- (Info^.Location.ClassName, Info^.Location.ProcedureName);
- if RoutineName <> '' then
- RoutineName := '.' + RoutineName;
- if Info^.Location.LineNumber > 0 then
- LineInfo := Format(',%d[%d]',
- [Info^.Location.LineNumber, Info^.Location.ProcOffsetLine])
- else
- LineInfo := '';
- Result := ModuleName + UnitName + RoutineName + LineInfo;
- end;
- // Formats entire call stack
- function TMessageBoxDetailedFormatter.GetStrings: TStrings;
- var
- ThreadID: Cardinal;
- I: Integer;
- Line: String;
- Stack: TEurekaBaseStackList;
- begin
- if not Assigned(FStr) then
- begin
- FStr := TStringList.Create;
- FModified := True;
- end;
- if FModified then
- begin
- Stack := CallStack;
- CalculateLengths;
- FStr.BeginUpdate;
- try
- FStr.Clear;
- FStr.Capacity := Stack.Count;
- if Stack.Count > 0 then
- begin
- ThreadID := Stack.Items[0].ThreadID;
- for I := 0 to Stack.Count - 1 do
- begin
- if (Stack.Items[I].Location.Module <> 0) and
- (Stack.Items[I].Location.DebugDetail in [ddUnit..ddSourceCode]) and
- (Stack.Items[I].ThreadID = ThreadID) then
- begin
- Line := GetItemText(I);
- if (FStr.Count <= 0) or (FStr[FStr.Count - 1] <> Line) then
- FStr.Add(Line);
- end;
- end;
- end;
- finally
- FStr.EndUpdate;
- end;
- FModified := False;
- end;
- Result := FStr;
- end;
- // Append call stack to error message
- function TMessageBoxDetailedDialog.ExceptionMessage: String;
- const
- MaxLines = 15;
- var
- Formatter: TMessageBoxDetailedFormatter;
- Stack: TEurekaBaseStackList;
- begin
- {$WARNINGS OFF}
- // Abstract methods are intended here.
- // It is like assert: they should not be called.
- Formatter := TMessageBoxDetailedFormatter.Create;
- {$WARNINGS ON}
- try
- if Assigned(CallStack) then
- Formatter.Assign(CallStack.Formatter);
- Formatter.CaptionHeader := '';
- Stack := nil;
- try
- if CallStack <> nil then
- begin
- Stack := TEurekaStackList.Create;
- Stack.Assign(CallStack);
- while Stack.Count > MaxLines do
- Stack.Delete(Stack.Count - 1);
- end;
- Result := inherited ExceptionMessage + sLineBreak + sLineBreak +
- CallStackToString(Stack, '', Formatter);
- finally
- FreeAndNil(Stack);
- end;
- finally
- FreeAndNil(Formatter);
- end;
- end;
- ...
- initialization
- RegisterDialogClass(TNullDialog);
- RegisterDialogClass(TMessageBoxDialog);
- RegisterDialogClass(TMessageBoxDetailedDialog);
- RegisterDialogClass(TRTLHandlerDialog);
- end.
- // Usage:
- CurrentEurekaModuleOptions.ExceptionDialogType := TMessageBoxDetailedDialog.ClassName;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement