source: trunk/Packages/Common/UJobProgressView.pas

Last change on this file was 38, checked in by chronos, 7 years ago
  • Modified: Updated Common package.
File size: 18.0 KB
Line 
1unit UJobProgressView;
2
3{$MODE Delphi}
4
5interface
6
7uses
8 SysUtils, Variants, Classes, Graphics, Controls, Forms, Syncobjs,
9 Dialogs, ComCtrls, StdCtrls, ExtCtrls, Contnrs, UThreading, Math,
10 DateUtils;
11
12const
13 EstimatedTimeShowTreshold = 4;
14 EstimatedTimeShowTresholdTotal = 1;
15 UpdateInterval = 100; // ms
16
17type
18
19 { TProgress }
20
21 TProgress = class
22 private
23 FLock: TCriticalSection;
24 FOnChange: TNotifyEvent;
25 FText: string;
26 FValue: Integer;
27 FMax: Integer;
28 procedure SetMax(const AValue: Integer);
29 procedure SetText(AValue: string);
30 procedure SetValue(const AValue: Integer);
31 public
32 procedure Increment;
33 procedure Reset;
34 constructor Create;
35 destructor Destroy; override;
36 property Value: Integer read FValue write SetValue;
37 property Max: Integer read FMax write SetMax;
38 property Text: string read FText write SetText;
39 property OnChange: TNotifyEvent read FOnChange write FOnChange;
40 end;
41
42 TFormJobProgressView = class;
43 TJobProgressView = class;
44 TJobThread = class;
45 TJob = class;
46
47 TJobProgressViewMethod = procedure(Job: TJob) of object;
48
49 { TJob }
50
51 TJob = class
52 private
53 FTerminate: Boolean;
54 procedure SetTerminate(const AValue: Boolean);
55 public
56 StartTime: TDateTime;
57 EndTime: TDateTime;
58 ProgressView: TJobProgressView;
59 Title: string;
60 Method: TJobProgressViewMethod;
61 NoThreaded: Boolean;
62 WaitFor: Boolean;
63 Progress: TProgress;
64 Thread: TJobThread;
65 ResultString: string;
66 Finished: Boolean;
67 procedure AddLogItem(Value: string);
68 constructor Create;
69 destructor Destroy; override;
70 property Terminate: Boolean read FTerminate write SetTerminate;
71 end;
72
73 TJobs = class(TObjectList)
74 end;
75
76 TJobThread = class(TListedThread)
77 procedure Execute; override;
78 public
79 ProgressView: TJobProgressView;
80 Job: TJob;
81 end;
82
83 { TFormJobProgressView }
84
85 TFormJobProgressView = class(TForm)
86 ImageList1: TImageList;
87 LabelText: TLabel;
88 Label2: TLabel;
89 LabelOperation: TLabel;
90 LabelEstimatedTimePart: TLabel;
91 LabelEstimatedTimeTotal: TLabel;
92 ListViewJobs: TListView;
93 MemoLog: TMemo;
94 PanelText: TPanel;
95 PanelProgressTotal: TPanel;
96 PanelOperationsTitle: TPanel;
97 PanelLog: TPanel;
98 PanelOperations: TPanel;
99 PanelProgress: TPanel;
100 ProgressBarPart: TProgressBar;
101 ProgressBarTotal: TProgressBar;
102 TimerUpdate: TTimer;
103 procedure FormHide(Sender: TObject);
104 procedure FormShow(Sender: TObject);
105 procedure ReloadJobList;
106 procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
107 procedure FormDestroy(Sender: TObject);
108 procedure ListViewJobsData(Sender: TObject; Item: TListItem);
109 procedure TimerUpdateTimer(Sender: TObject);
110 procedure FormCreate(Sender: TObject);
111 procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
112 procedure UpdateHeight;
113 public
114 JobProgressView: TJobProgressView;
115 end;
116
117 { TJobProgressView }
118
119 TJobProgressView = class(TComponent)
120 private
121 FAutoClose: Boolean;
122 Finished: Boolean;
123 FOnJobFinish: TJobProgressViewMethod;
124 FOnOwnerDraw: TNotifyEvent;
125 FOwnerDraw: Boolean;
126 FShowDelay: Integer;
127 FTerminate: Boolean;
128 FormList: TList;
129 TotalStartTime: TDateTime;
130 Log: TStringList;
131 FForm: TFormJobProgressView;
132 procedure SetTerminate(const AValue: Boolean);
133 procedure UpdateProgress;
134 procedure JobProgressChange(Sender: TObject);
135 public
136 Jobs: TJobs;
137 CurrentJob: TJob;
138 CurrentJobIndex: Integer;
139 constructor Create(TheOwner: TComponent); override;
140 destructor Destroy; override;
141 procedure Clear;
142 function AddJob(Title: string; Method: TJobProgressViewMethod;
143 NoThreaded: Boolean = False; WaitFor: Boolean = False): TJob;
144 procedure Start;
145 procedure Stop;
146 procedure TermSleep(Delay: Integer);
147 property Form: TFormJobProgressView read FForm;
148 property Terminate: Boolean read FTerminate write SetTerminate;
149 published
150 property OwnerDraw: Boolean read FOwnerDraw write FOwnerDraw;
151 property ShowDelay: Integer read FShowDelay write FShowDelay;
152 property AutoClose: Boolean read FAutoClose write FAutoClose;
153 property OnJobFinish: TJobProgressViewMethod read FOnJobFinish
154 write FOnJobFinish;
155 property OnOwnerDraw: TNotifyEvent read FOnOwnerDraw
156 write FOnOwnerDraw;
157 end;
158
159 //var
160 // FormJobProgressView: TFormJobProgressView;
161
162procedure Register;
163
164resourcestring
165 SExecuted = 'Executed';
166
167implementation
168
169{$R *.lfm}
170
171resourcestring
172 SPleaseWait = 'Please wait...';
173 STerminate = 'Termination';
174 SEstimatedTime = 'Estimated time: %s';
175 STotalEstimatedTime = 'Total estimated time: %s';
176 SFinished = 'Finished';
177 SOperations = 'Operations:';
178
179procedure Register;
180begin
181 RegisterComponents('Common', [TJobProgressView]);
182end;
183
184{ TJobThread }
185
186procedure TJobThread.Execute;
187begin
188 try
189 try
190 //raise Exception.Create('Exception in job');
191 ProgressView.CurrentJob.Method(Job);
192 except
193 on E: Exception do begin
194 ProgressView.Terminate := True;
195 raise;
196 end;
197 end;
198 finally
199 Terminate;
200 end;
201end;
202
203{ TFormJobProgressView }
204
205procedure TFormJobProgressView.UpdateHeight;
206var
207 H: Integer;
208 PanelOperationsVisible: Boolean;
209 PanelOperationsHeight: Integer;
210 PanelProgressVisible: Boolean;
211 PanelProgressTotalVisible: Boolean;
212 PanelLogVisible: Boolean;
213 MemoLogHeight: Integer = 200;
214 I: Integer;
215 ItemRect: TRect;
216 MaxH: Integer;
217begin
218 H := PanelOperationsTitle.Height;
219 PanelOperationsVisible := JobProgressView.Jobs.Count > 0;
220 if PanelOperationsVisible <> PanelOperations.Visible then
221 PanelOperations.Visible := PanelOperationsVisible;
222 if ListViewJobs.Items.Count > 0 then begin
223 Maxh := 0;
224 for I := 0 to ListViewJobs.Items.Count - 1 do
225 begin
226 ItemRect := ListViewJobs.Items[i].DisplayRect(drBounds);
227 Maxh := Max(Maxh, ItemRect.Top + (ItemRect.Bottom - ItemRect.Top));
228 end;
229 PanelOperationsHeight := Scale96ToScreen(12) + Maxh;
230 end else PanelOperationsHeight := Scale96ToScreen(8);
231 if PanelOperationsHeight <> PanelOperations.Height then
232 PanelOperations.Height := PanelOperationsHeight;
233 if PanelOperationsVisible then
234 H := H + PanelOperations.Height;
235
236 PanelProgressVisible := (JobProgressView.Jobs.Count > 0) and not JobProgressView.Finished;
237 if PanelProgressVisible <> PanelProgress.Visible then
238 PanelProgress.Visible := PanelProgressVisible;
239 if PanelProgressVisible then
240 H := H + PanelProgress.Height;
241 PanelProgressTotalVisible := (JobProgressView.Jobs.Count > 1) and not JobProgressView.Finished;
242 if PanelProgressTotalVisible <> PanelProgressTotal.Visible then
243 PanelProgressTotal.Visible := PanelProgressTotalVisible;
244 if PanelProgressTotalVisible then
245 H := H + PanelProgressTotal.Height;
246 Constraints.MinHeight := H;
247 PanelLogVisible := MemoLog.Lines.Count > 0;
248 if PanelLogVisible <> PanelLog.Visible then
249 PanelLog.Visible := PanelLogVisible;
250 if PanelLogVisible then
251 H := H + Scale96ToScreen(MemoLogHeight);
252 if PanelText.Visible then
253 H := H + PanelText.Height;
254 if Height <> H then begin
255 Height := H;
256 Top := (Screen.Height - H) div 2;
257 end;
258end;
259
260procedure TFormJobProgressView.TimerUpdateTimer(Sender: TObject);
261var
262 ProgressBarPartVisible: Boolean;
263 ProgressBarTotalVisible: Boolean;
264begin
265 JobProgressView.UpdateProgress;
266 if Visible and (not ProgressBarPart.Visible) and
267 Assigned(JobProgressView.CurrentJob) and
268 (JobProgressView.CurrentJob.Progress.Value > 0) then begin
269 ProgressBarPartVisible := True;
270 if ProgressBarPartVisible <> ProgressBarPart.Visible then
271 ProgressBarPart.Visible := ProgressBarPartVisible;
272 ProgressBarTotalVisible := True;
273 if ProgressBarTotalVisible <> ProgressBarTotal.Visible then
274 ProgressBarTotal.Visible := ProgressBarTotalVisible;
275 end;
276 if not Visible then begin
277 TimerUpdate.Interval := UpdateInterval;
278 if not JobProgressView.OwnerDraw then Show;
279 end;
280 if Assigned(JobProgressView.CurrentJob) then begin
281 LabelText.Caption := JobProgressView.CurrentJob.Progress.Text;
282 if LabelText.Caption <> '' then begin
283 PanelText.Visible := True;
284 UpdateHeight;
285 end;
286 end;
287end;
288
289procedure TFormJobProgressView.FormDestroy(Sender:TObject);
290begin
291end;
292
293procedure TFormJobProgressView.ListViewJobsData(Sender: TObject; Item: TListItem);
294begin
295 if (Item.Index >= 0) and (Item.Index < JobProgressView.Jobs.Count) then
296 with TJob(JobProgressView.Jobs[Item.Index]) do begin
297 Item.Caption := Title;
298 if Item.Index = JobProgressView.CurrentJobIndex then Item.ImageIndex := 1
299 else if Finished then Item.ImageIndex := 0
300 else Item.ImageIndex := 2;
301 Item.Data := JobProgressView.Jobs[Item.Index];
302 end;
303end;
304
305procedure TFormJobProgressView.FormClose(Sender: TObject;
306 var CloseAction: TCloseAction);
307begin
308end;
309
310procedure TFormJobProgressView.FormCreate(Sender: TObject);
311begin
312 Caption := SPleaseWait;
313 try
314 //Animate1.FileName := ExtractFileDir(UTF8Encode(Application.ExeName)) +
315 // DirectorySeparator + 'horse.avi';
316 //Animate1.Active := True;
317 except
318
319 end;
320end;
321
322procedure TFormJobProgressView.ReloadJobList;
323begin
324 // Workaround for not showing first line
325 //Form.ListViewJobs.Items.Count := Jobs.Count + 1;
326 //Form.ListViewJobs.Refresh;
327
328 if ListViewJobs.Items.Count <> JobProgressView.Jobs.Count then
329 ListViewJobs.Items.Count := JobProgressView.Jobs.Count;
330 ListViewJobs.Refresh;
331 Application.ProcessMessages;
332 UpdateHeight;
333end;
334
335procedure TFormJobProgressView.FormShow(Sender: TObject);
336begin
337 ReloadJobList;
338end;
339
340procedure TFormJobProgressView.FormHide(Sender: TObject);
341begin
342 JobProgressView.Jobs.Clear;
343 ReloadJobList;
344end;
345
346procedure TFormJobProgressView.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
347begin
348 CanClose := JobProgressView.Finished;
349 JobProgressView.Terminate := True;
350 Caption := SPleaseWait + STerminate;
351end;
352
353
354{ TJobProgressView }
355
356function TJobProgressView.AddJob(Title: string; Method: TJobProgressViewMethod;
357 NoThreaded: Boolean = False; WaitFor: Boolean = False): TJob;
358begin
359 Result := TJob.Create;
360 Result.ProgressView := Self;
361 Result.Title := Title;
362 Result.Method := Method;
363 Result.NoThreaded := NoThreaded;
364 Result.WaitFor := WaitFor;
365 Result.Progress.Max := 100;
366 Result.Progress.Reset;
367 Result.Progress.OnChange := JobProgressChange;
368 Jobs.Add(Result);
369 //ReloadJobList;
370end;
371
372procedure TJobProgressView.Start;
373var
374 I: Integer;
375begin
376 Terminate := False;
377
378 if not OwnerDraw then Form.BringToFront;
379
380 Finished := False;
381 Form.Caption := SPleaseWait;
382 try
383 FormList := Screen.DisableForms(Form);
384 Log.Clear;
385 Form.MemoLog.Clear;
386
387 Form.PanelText.Visible := False;
388 Form.LabelEstimatedTimePart.Visible := False;
389 Form.LabelEstimatedTimeTotal.Visible := False;
390
391 CurrentJob := nil;
392 if ShowDelay = 0 then begin
393 Form.TimerUpdate.Interval := UpdateInterval;
394 Form.TimerUpdate.Enabled := True;
395 Form.TimerUpdateTimer(Self);
396 end else begin
397 Form.TimerUpdate.Interval := ShowDelay;
398 Form.TimerUpdate.Enabled := True;
399 end;
400
401 TotalStartTime := Now;
402 Form.ProgressBarTotal.Position := 0;
403 Form.ProgressBarTotal.Visible := False;
404 //UpdateHeight;
405
406 I := 0;
407 while I < Jobs.Count do
408 with TJob(Jobs[I]) do begin
409 CurrentJobIndex := I;
410 CurrentJob := TJob(Jobs[I]);
411 JobProgressChange(Self);
412 StartTime := Now;
413 Form.LabelEstimatedTimePart.Caption := Format(SEstimatedTime, ['']);
414 Form.ProgressBarPart.Position := 0;
415 Form.ProgressBarPart.Visible := False;
416 //Show;
417 Form.ReloadJobList;
418 Application.ProcessMessages;
419 if NoThreaded then begin
420 Thread := nil;
421 Method(CurrentJob);
422 end else begin
423 try
424 Thread := TJobThread.Create(True);
425 with Thread do begin
426 FreeOnTerminate := False;
427 Job := CurrentJob;
428 Name := 'Job: ' + Job.Title;
429 ProgressView := Self;
430 Start;
431 while not Terminated do begin
432 Application.ProcessMessages;
433 Sleep(1);
434 end;
435 WaitFor;
436 end;
437 finally
438 FreeAndNil(Thread);
439 end;
440 end;
441 Form.ProgressBarPart.Hide;
442 if Assigned(FOnJobFinish) then
443 FOnJobFinish(CurrentJob);
444 if Terminate then Break;
445 EndTime := Now;
446 Finished := True;
447 Inc(I);
448 end;
449 finally
450 CurrentJob := nil;
451 Form.TimerUpdate.Enabled := False;
452 Screen.EnableForms(FormList);
453 //if Visible then Hide;
454 Form.MemoLog.Lines.Assign(Log);
455 if (Form.MemoLog.Lines.Count = 0) and FAutoClose then begin
456 Form.Hide;
457 end;
458 if not Form.Visible then Clear;
459 Form.Caption := SFinished;
460 //LabelEstimatedTimePart.Visible := False;
461 Finished := True;
462 CurrentJobIndex := -1;
463 Form.ReloadJobList;
464 end;
465end;
466
467procedure TJobProgressView.JobProgressChange(Sender: TObject);
468begin
469 if Assigned(FOnOwnerDraw) then
470 FOnOwnerDraw(Self);
471end;
472
473procedure TJobProgressView.Stop;
474begin
475 Terminate := True;
476end;
477
478procedure TJobProgressView.TermSleep(Delay: Integer);
479const
480 Quantum = 100;
481var
482 I: Integer;
483begin
484 Sleep(Delay mod Quantum);
485 for I := 1 to (Delay div Quantum) do begin
486 if Terminate then Break;
487 Sleep(Quantum);
488 end;
489end;
490
491procedure TJobProgressView.SetTerminate(const AValue: Boolean);
492var
493 I: Integer;
494begin
495 if AValue = FTerminate then Exit;
496 for I := 0 to Jobs.Count - 1 do
497 TJob(Jobs[I]).Terminate := AValue;
498 FTerminate := AValue;
499end;
500
501procedure TJobProgressView.UpdateProgress;
502const
503 OneJobValue: Integer = 100;
504var
505 TotalMax: Integer;
506 TotalValue: Integer;
507 EstimatedTimePart: TDateTime;
508 RemainingTime: TDateTime;
509begin
510 if Assigned(CurrentJob) then
511 with CurrentJob, Form do begin
512 // Part progress
513 ProgressBarPart.Max := Progress.Max;
514 ProgressBarPart.Position := Progress.Value;
515 if (Progress.Value >= EstimatedTimeShowTreshold) then begin
516 EstimatedTimePart := (Now - StartTime) / Progress.Value * (Progress.Max - Progress.Value);
517 LabelEstimatedTimePart.Caption := Format(SEstimatedTime, [
518 TimeToStr(EstimatedTimePart)]);
519 LabelEstimatedTimePart.Visible := True;
520 end;
521
522 // Total progress
523 TotalMax := Jobs.Count * OneJobValue;
524 TotalValue := Int64(CurrentJobIndex) * OneJobValue +
525 Round(Progress.Value / Progress.Max * OneJobValue);
526 ProgressBarTotal.Max := TotalMax;
527 ProgressBarTotal.Position := TotalValue;
528 if (TotalValue >= EstimatedTimeShowTresholdTotal) then begin
529 // Project estimated time according part estimated time plus
530 // estimated time by elapsed time divided by elapsed ticks mutiplied by rest ticks
531 RemainingTime := EstimatedTimePart +
532 (Now - TotalStartTime + EstimatedTimePart) /
533 ((CurrentJobIndex + 1) * OneJobValue) *
534 ((Jobs.Count - 1 - CurrentJobIndex) * OneJobValue);
535 if (RemainingTime > 0) and (RemainingTime < EncodeDate(2100, 1, 1)) then begin
536 LabelEstimatedTimeTotal.Caption := Format(STotalEstimatedTime, [
537 TimeToStr(RemainingTime)]);
538 LabelEstimatedTimeTotal.Visible := True;
539 end else begin
540 LabelEstimatedTimeTotal.Visible := False;
541 end;
542 end;
543 end;
544end;
545
546constructor TJobProgressView.Create(TheOwner: TComponent);
547begin
548 inherited;
549 if not (csDesigning in ComponentState) then begin
550 FForm := TFormJobProgressView.Create(Self);
551 FForm.JobProgressView := Self;
552 end;
553 Jobs := TJobs.Create;
554 Log := TStringList.Create;
555 //PanelOperationsTitle.Height := 80;
556 AutoClose := True;
557 ShowDelay := 0;
558end;
559
560procedure TJobProgressView.Clear;
561begin
562 Jobs.Clear;
563 Log.Clear;
564 //ReloadJobList;
565end;
566
567destructor TJobProgressView.Destroy;
568begin
569 FreeAndNil(Log);
570 FreeAndNil(Jobs);
571 inherited;
572end;
573
574{ TProgress }
575
576procedure TProgress.SetMax(const AValue: Integer);
577begin
578 try
579 FLock.Acquire;
580 FMax := AValue;
581 if FMax < 1 then FMax := 1;
582 if FValue >= FMax then FValue := FMax;
583 finally
584 FLock.Release;
585 end;
586end;
587
588procedure TProgress.SetText(AValue: string);
589begin
590 try
591 FLock.Acquire;
592 if FText = AValue then Exit;
593 FText := AValue;
594 finally
595 FLock.Release;
596 end;
597end;
598
599procedure TProgress.SetValue(const AValue: Integer);
600var
601 Change: Boolean;
602begin
603 try
604 FLock.Acquire;
605 if AValue < Max then begin
606 Change := AValue <> FValue;
607 FValue := AValue;
608 if Change and Assigned(FOnChange) then
609 try
610 FLock.Release;
611 FOnChange(Self);
612 finally
613 FLock.Acquire;
614 end;
615 end;
616 finally
617 FLock.Release;
618 end;
619end;
620
621procedure TProgress.Increment;
622begin
623 try
624 FLock.Acquire;
625 Value := Value + 1;
626 finally
627 FLock.Release;
628 end;
629end;
630
631procedure TProgress.Reset;
632begin
633 try
634 FLock.Acquire;
635 FValue := 0;
636 finally
637 FLock.Release;
638 end;
639end;
640
641constructor TProgress.Create;
642begin
643 FMax := 100;
644 FLock := TCriticalSection.Create;
645end;
646
647destructor TProgress.Destroy;
648begin
649 FLock.Free;
650 inherited Destroy;
651end;
652
653{ TJob }
654
655procedure TJob.SetTerminate(const AValue: Boolean);
656begin
657 if FTerminate = AValue then Exit;
658 FTerminate := AValue;
659 if AValue then begin
660 ProgressView.Terminate := AValue;
661 if Assigned(Thread) then Thread.Terminate;
662 end;
663end;
664
665procedure TJob.AddLogItem(Value: string);
666begin
667 with ProgressView do begin
668 Log.Add(Value);
669 end;
670end;
671
672constructor TJob.Create;
673begin
674 Progress := TProgress.Create;
675 Terminate := False;
676 Finished := False;
677end;
678
679destructor TJob.Destroy;
680begin
681 Progress.Free;
682 inherited;
683end;
684
685end.
Note: See TracBrowser for help on using the repository browser.