source: trunk/Packages/Common/JobProgressView.pas

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