What you should do is to separate the worker thread from the UI form. Do your work in a TThread descendant.
All the communication between the two should be done by posting asynchronous messages. The worker thread should post messages to the UI when it starts, to report progress, and when it ends. The UI can use this to visualize the form, update the progress bars and eventually close the form. In case you need to have a user cancel button on the UI, this should post a message to the worker thread that it uses to abort the execution.
By "posting asynchronous messages" I mean you should either use a messaging framework other that the RTL.Messaging (it only handles synchronous messages) or at least wrap the calls in TThread.Queue. I do all the messaging with my messaging classes explained here.
To handle the work I myself use this base class:
TMEBatchOp = Class(TMELocalizableComponent)
Private
FMessageHandler: TMEMessageHandler;
FInfo: TMEBatchOpInfo;
FLastUpdateStatus: Cardinal;
FRequestedCancel: Boolean;
Procedure CheckCancelled;
Procedure Run;
Procedure SetResult(Const Value: TMEBatchOpResult);
Procedure SetStatus(Const Value: TMEBatchOpStatus);
Procedure UpdateStatus(Const DelaySecs: Integer = 0);
Protected
Function CalcWorkMax: Integer; Virtual;
Procedure DoExecute; Virtual; Abstract;
Procedure DoSetup; Virtual;
Procedure DoTeardown; Virtual;
Function GetAllowCancel: Boolean; Virtual;
Function GetDescription: String; Virtual;
Procedure ReceiveEnvelope(Const Envelope: TMEMessageEnvelope);
Property MessageHandler: TMEMessageHandler Read FMessageHandler;
Property Info: TMEBatchOpInfo Read FInfo;
Property RequestedCancel: Boolean Read FRequestedCancel;
Public
Class Procedure CaptureLocalizable(Localizer: TMELocalizer); Override;
Class Function GetModuleName: String;
Constructor Create(AOwner: TComponent); Override;
Procedure Worked(Qty: Integer = 1);
End;
Implementation of a specific worker thread requires defining a subclass of TMEBatchOp and implementing the DoExecute method. Optionally CalcWorkMax, DoSetup, and DoTeardown can also be redefined if needed. For Example:
Type
TBatchOpSaveTexts = Class(TMEBatchOp)
Protected
Function CalcWorkMax: Integer; Override;
Procedure DoExecute; Override;
Function GetDescription: String; Override;
Public
Class Procedure CaptureLocalizable(Localizer: TMELocalizer); Override;
End;
Executing this just requires a call like this:
RunAndFree(TBatchOpSaveTexts);
Where RunAndFree is implemented like this:
Function RunAndFree(BatchOpClass: TMEBatchOpClass): TMEBatchOpResult; Overload;
Begin
Result := RunAndFree(BatchOpClass.Create(Nil));
End;
Function RunAndFree(BatchOp: TMEBatchOp): TMEBatchOpResult; Overload;
Begin
Try
BatchOp.Run;
Result := BatchOp.Info.Result;
Finally
BatchOp.Free;
End;
End;
TMEBatchOpInfo contains info on work result and status, times and work done and to do and progress. It also contains info on whether the operation can be cancelled. All messages contain a copy of this info, so the UI can update itself accordingly. Here is its interface:
TMEBatchOpInfo = Class(TMEPersistent)
Private
FID: String;
FParentID: String;
FDescription: String;
FTimeBegin: TDateTime;
FTimeEnd: TDateTime;
FResult: TMEBatchOpResult;
FWorkMax: Integer;
FWorkDone: Integer;
FAllowCancel: Boolean;
FStatus: TMEBatchOpStatus;
Function GetDone: Boolean;
Public
Constructor Create; Override;
Procedure Assign(Source: TPersistent); Override;
Property ID: String Read FID;
Property ParentID: String Read FID;
Property Description: String Read FDescription;
Property TimeBegin: TDateTime Read FTimeBegin;
Property TimeEnd: TDateTime Read FTimeEnd;
Property Result: TMEBatchOpResult Read FResult;
Property WorkMax: Integer Read FWorkMax;
Property WorkDone: Integer Read FWorkDone;
Property Done: Boolean Read GetDone;
Property AllowCancel: Boolean Read FAllowCancel;
Property Status: TMEBatchOpStatus Read FStatus;
End;