1
0
mirror of https://github.com/loginov-dmitry/multithread.git synced 2025-12-25 15:36:56 +02:00
Files
multithread/ExWaitWindow/LDSWaitFrm.pas
2024-10-12 21:58:53 +03:00

427 lines
13 KiB
ObjectPascal

{$IFDEF FPC}
{$MODE DELPHI}{$H+}{$CODEPAGE UTF8}
{$ENDIF}
{
Copyright (c) 2021, Loginov Dmitry Sergeevich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
unit LDSWaitFrm;
interface
uses
{$IFnDEF FPC}
Windows, Messages,
{$ELSE}
LCLIntf, LCLType, LMessages,
{$ENDIF}
SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, {$IfNDef FPC}ActiveX, {$EndIf}ExtCtrls, Buttons, SyncObjs, ComCtrls, ParamsUtils, LDSWaitIntf;
{$IFnDEF FPC}
{$IF RTLVersion >= 20.00}
{$DEFINE D2009PLUS}
{$IFEND}
{$ENDIF}
type
{$IFDEF D2009PLUS}
// Для современных версий Delphi используется механизм анонимных функций.
TWorkFunction = reference to function (OperType: Integer; AParams: TParamsRec; AResParams: PParamsRec; wsi: IWaitStatusInterface): Boolean;
{$ELSE}
// Для старых версий Delphi приходится объявлять отдельно TWorkFunction и TWorkMethod
TWorkFunction = function (OperType: Integer; AParams: TParamsRec; AResParams: PParamsRec; wsi: IWaitStatusInterface): Boolean;
TWorkMethod = function (OperType: Integer; AParams: TParamsRec; AResParams: PParamsRec; wsi: IWaitStatusInterface): Boolean of object;
{$ENDIF}
TLDSWaitForm = class(TForm)
Label1: TLabel;
labOperationName: TLabel;
Label3: TLabel;
Label4: TLabel;
lbTime: TLabel;
Timer1: TTimer;
btnStop: TSpeedButton;
labWaitStatus: TLabel;
ProgressBar1: TProgressBar;
procedure FormShow(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure btnStopClick(Sender: TObject);
private
AThread: TThread;
FError: string;
FIsSuccess: Boolean;
FStartTime: TDateTime;
FCanClose: Boolean;
FStatusInterface: IWaitStatusInterface;
public
{ Public declarations }
end;
function DoOperationInThread(AOwner: TForm; OperType: Integer; OperationName: string; AParams: TParamsRec;
WorkFunc: TWorkFunction; ShowStopButton: Boolean; AResParams: PParamsRec = RES_PARAMS_NIL): Boolean; overload;
{$IFNDEF D2009PLUS}
// Данный вариант используется только для поддержки старых версий Delphi
function DoOperationInThread(AOwner: TForm; OperType: Integer; OperationName: string; AParams: TParamsRec;
WorkMethod: TWorkMethod; ShowStopButton: Boolean; AResParams: PParamsRec = RES_PARAMS_NIL): Boolean; overload;
{$ENDIF}
implementation
{$IFnDEF FPC}
{$R *.dfm}
{$ELSE}
{$R *.lfm}
{$ENDIF}
type
TWaitStatusControl = class(TInterfacedObject, IWaitStatusInterface)
private
FStatusText: TStringList;
FOperationName: string;
FCritSect: TCriticalSection;
FNeedStop: Boolean;
FProgressPosition: Integer;
FProgressMin: Integer;
FProgressMax: Integer;
public
function GetOperationName: string;
procedure SetOperationName(const Value: string);
function GetStatusText: string;
procedure SetStatusText(const Value: string);
function GetNeedStop: Boolean;
procedure SetNeedStop(const Value: Boolean);
function GetStatusLine(LineNum: Integer): string;
procedure SetStatusLine(LineNum: Integer; const Value: string);
procedure ClearStatusText;
procedure CheckNeedStop;
procedure SetProgressPosition(Value: Double);
function GetProgressPosition: Double;
procedure SetProgressMinMax(AMin, AMax: Double);
function GetProgressMin: Integer;
function GetProgressMax: Integer;
public
constructor Create;
destructor Destroy; override;
end;
TBackgroundOperationsThread = class(TThread)
public
FParams: TParamsRec;
FResParams: PParamsRec;
FWorkFunc: TWorkFunction;
{$IFNDEF D2009PLUS}
FWorkMethod: TWorkMethod;
{$ENDIF}
FForm: TForm;
// Эвент нужен для того, чтобы доп. поток немедленно отреагирован на отображения
// окна ожидания на экране.
FEvent: TEvent;
FStatusInterface: IWaitStatusInterface;
FOperType: Integer;
protected
procedure Execute; override;
public
constructor Create(AParams: TParamsRec; AResParams: PParamsRec; AWorkFunc: TWorkFunction; {$IFNDEF D2009PLUS}AWorkMethod: TWorkMethod; {$ENDIF}
AForm: TForm; AStatusInterface: IWaitStatusInterface; OperType: Integer);
destructor Destroy; override;
end;
function DoOperationInThreadInternal(AOwner: TForm; OperType: Integer; OperationName: string; AParams: TParamsRec;
WorkFunc: TWorkFunction; {$IFNDEF D2009PLUS}WorkMethod: TWorkMethod; {$ENDIF}ShowStopButton: Boolean; AResParams: PParamsRec): Boolean;
var
AForm: TLDSWaitForm;
begin
if GetCurrentThreadId <> MainThreadID then
raise Exception.Create('DoOperationInThreadInternal: Вызов должен происходить из главного потока');
if Assigned(AResParams) then
AResParams^.Clear;
AForm := TLDSWaitForm.Create(AOwner);
try
AForm.btnStop.Visible := ShowStopButton;
AForm.labOperationName.Caption := OperationName;
AForm.labOperationName.Left := (AForm.Width - AForm.labOperationName.Width) div 2;
AForm.labWaitStatus.Caption := '';
AForm.FStatusInterface := TWaitStatusControl.Create;
AForm.AThread := TBackgroundOperationsThread.Create(AParams, AResParams, WorkFunc, {$IFNDEF D2009PLUS}WorkMethod, {$ENDIF}AForm, AForm.FStatusInterface, OperType);
AForm.ShowModal;
if AForm.FError <> '' then
raise Exception.Create(AForm.FError);
Result := AForm.FIsSuccess;
finally
AForm.AThread.Free;
AForm.Free;
end;
end;
function DoOperationInThread(AOwner: TForm; OperType: Integer; OperationName: string; AParams: TParamsRec;
WorkFunc: TWorkFunction; ShowStopButton: Boolean; AResParams: PParamsRec = RES_PARAMS_NIL): Boolean;
begin
Result := DoOperationInThreadInternal(AOwner, OperType, OperationName, AParams,
WorkFunc, {$IFNDEF D2009PLUS}nil, {$ENDIF}ShowStopButton, AResParams);
end;
{$IFNDEF D2009PLUS}
function DoOperationInThread(AOwner: TForm; OperType: Integer; OperationName: string; AParams: TParamsRec;
WorkMethod: TWorkMethod; ShowStopButton: Boolean; AResParams: PParamsRec = RES_PARAMS_NIL): Boolean; overload;
begin
Result := DoOperationInThreadInternal(AOwner, OperType, OperationName, AParams,
nil, WorkMethod, ShowStopButton, AResParams);
end;
{$ENDIF}
{ TBackgroundOperationsThread }
constructor TBackgroundOperationsThread.Create(AParams: TParamsRec; AResParams: PParamsRec;
AWorkFunc: TWorkFunction; {$IFNDEF D2009PLUS}AWorkMethod: TWorkMethod; {$ENDIF}AForm: TForm;
AStatusInterface: IWaitStatusInterface; OperType: Integer);
const
STATE_NONSIGNALED = FALSE;
NOT_AUTO_RESET = TRUE;
begin
inherited Create(False);
FParams := AParams;
FResParams := AResParams;
FWorkFunc := AWorkFunc;
{$IFNDEF D2009PLUS}
FWorkMethod := AWorkMethod;
{$ENDIF}
FForm := AForm;
FEvent := TEvent.Create(nil, NOT_AUTO_RESET, STATE_NONSIGNALED, '', False);
FStatusInterface := AStatusInterface;
FOperType := OperType;
end;
destructor TBackgroundOperationsThread.Destroy;
begin
FEvent.SetEvent; // На всякий случай
inherited;
FEvent.Free;
end;
procedure TBackgroundOperationsThread.Execute;
begin
try
{$IfNDef FPC}CoInitialize(nil){$EndIf};
try
if Assigned(FWorkFunc) then
TLDSWaitForm(FForm).FIsSuccess := FWorkFunc(FOperType, FParams, FResParams, TLDSWaitForm(FForm).FStatusInterface)
{$IFNDEF D2009PLUS}
else if Assigned(FWorkMethod) then
TLDSWaitForm(FForm).FIsSuccess := FWorkMethod(FOperType, FParams, FResParams, TLDSWaitForm(FForm).FStatusInterface)
{$ENDIF}
finally
{$IfNDef FPC}CoUnInitialize(){$EndIf};
end;
except
on E: Exception do
TLDSWaitForm(FForm).FError := 'Ошибка в потоке: ' + E.Message;
end;
// Ожидаем, когда форма появится на экране
FEvent.WaitFor(INFINITE);
// Выставляем форме разрешение за закрытие
TLDSWaitForm(FForm).FCanClose := True;
// Посылаем форме сообщение о закрытии
SendMessage(TLDSWaitForm(FForm).Handle, {$IfDef FPC}LM_CLOSEQUERY{$Else}WM_CLOSE{$EndIf}, 0, 0);
end;
procedure TLDSWaitForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
CanClose := FCanClose;
end;
procedure TLDSWaitForm.FormShow(Sender: TObject);
begin
FStartTime := Now;
// Сообщаем потоку о появлении формы на экране
TBackgroundOperationsThread(AThread).FEvent.SetEvent;
end;
procedure TLDSWaitForm.btnStopClick(Sender: TObject);
begin
FStatusInterface.NeedStop := True;
end;
procedure TLDSWaitForm.Timer1Timer(Sender: TObject);
var
AMin, AMax: Integer;
begin
lbTime.Caption := FormatDateTime('nn:ss', Now - FStartTime);
if FStatusInterface.OperationName <> '' then
labOperationName.Caption := FStatusInterface.OperationName;
labOperationName.Left := (Width - labOperationName.Width) div 2;
labWaitStatus.Caption := FStatusInterface.StatusText;
ProgressBar1.Visible := FStatusInterface.ProgressPosition > 0;
AMin := FStatusInterface.GetProgressMin();
AMax := FStatusInterface.GetProgressMax();
if (ProgressBar1.Min <> AMin) or (ProgressBar1.Max <> AMax) then
begin
if AMax <= AMin then AMax := AMin + 1;
ProgressBar1.Max := MaxInt;
ProgressBar1.Min := AMin;
ProgressBar1.Max := AMax;
end;
ProgressBar1.Position := Round(FStatusInterface.ProgressPosition);
end;
{ TWaitStatusControl }
procedure TWaitStatusControl.CheckNeedStop;
begin
if FNeedStop then
raise Exception.Create('Пользователь нажал кнопку "Отмена"');
end;
procedure TWaitStatusControl.ClearStatusText;
begin
SetStatusText('');
end;
constructor TWaitStatusControl.Create;
begin
inherited;
FStatusText := TStringList.Create;
FCritSect := TCriticalSection.Create;
FProgressMax := 100;
end;
destructor TWaitStatusControl.Destroy;
begin
FStatusText.Free;
FCritSect.Free;
inherited;
end;
function TWaitStatusControl.GetNeedStop: Boolean;
begin
Result := FNeedStop;
end;
function TWaitStatusControl.GetOperationName: string;
begin
FCritSect.Enter;
Result := FOperationName;
FCritSect.Leave;
end;
function TWaitStatusControl.GetProgressMax: Integer;
begin
Result := FProgressMax;
end;
function TWaitStatusControl.GetProgressMin: Integer;
begin
Result := FProgressMin;
end;
function TWaitStatusControl.GetProgressPosition: Double;
begin
Result := FProgressPosition;
end;
function TWaitStatusControl.GetStatusLine(LineNum: Integer): string;
begin
Result := '';
if (LineNum < 1) or (LineNum > 4) then Exit;
FCritSect.Enter;
try
if FStatusText.Count < LineNum then Exit;
Result := FStatusText[LineNum - 1];
finally
FCritSect.Leave;
end;
end;
function TWaitStatusControl.GetStatusText: string;
begin
FCritSect.Enter;
Result := FStatusText.Text;
FCritSect.Leave;
end;
procedure TWaitStatusControl.SetNeedStop(const Value: Boolean);
begin
FNeedStop := Value;
end;
procedure TWaitStatusControl.SetOperationName(const Value: string);
begin
FCritSect.Enter;
FOperationName := Value;
FCritSect.Leave;
end;
procedure TWaitStatusControl.SetProgressMinMax(AMin, AMax: Double);
begin
FProgressMin := Round(AMin);
FProgressMax := Round(AMax);
end;
procedure TWaitStatusControl.SetProgressPosition(Value: Double);
begin
FProgressPosition := Round(Value);
end;
procedure TWaitStatusControl.SetStatusLine(LineNum: Integer;
const Value: string);
begin
if (LineNum < 1) or (LineNum > 4) then Exit;
FCritSect.Enter;
try
while FStatusText.Count < LineNum do
FStatusText.Add('');
FStatusText[LineNum - 1] := Value;
finally
FCritSect.Leave;
end;
end;
procedure TWaitStatusControl.SetStatusText(const Value: string);
begin
FCritSect.Enter;
FStatusText.Text := Value;
FCritSect.Leave;
end;
end.