source: trunk/Packages/Common/UJobProgressView.pas

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