source: trunk/Demo/Packages/Common/UJobProgressView.pas

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