source: trunk/Packages/Common/UJobProgressView.pas

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