source: trunk/Packages/Common/UJobProgressView.pas

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