Step-by-Step Guide: Implementing TAdvProgressBar for Long-Running TasksLong-running tasks—such as file transfers, data processing, or complex calculations—need clear visual feedback so users stay informed and confident that your application is working. TAdvProgressBar (part of TMS VCL UI Pack) is a versatile progress control for Delphi VCL applications that supports custom styles, animations, indeterminate modes, and fine-grained event handling. This guide walks through practical steps to implement TAdvProgressBar for long-running operations, improve responsiveness, and provide a polished user experience.
What you’ll learn
- How to add TAdvProgressBar to a form and configure basic properties
- Approaches for reporting progress from synchronous and asynchronous tasks
- Using indeterminate and marquee modes for tasks without known length
- Best practices for threading, responsiveness, and UI safety
- Enhancing UX with gradients, text overlays, and animations
- Debugging and performance tips
Prerequisites
- Delphi (XE8 or later recommended) with the TMS VCL UI Pack installed (TAdvProgressBar component available).
- Basic familiarity with Delphi forms, event handlers, and threading (TThread, TTask).
- A project with a long-running operation to demonstrate (file copy, heavy computation, or database processing).
1) Adding TAdvProgressBar to your form
- Open your Delphi project and the Form Designer.
- From the TMS VCL UI Pack palette, drop a TAdvProgressBar onto the form.
- Set alignment or anchors so the bar resizes with the form (eg. Align = alTop or use Anchors).
- Adjust basic properties:
- Min (default 0) and Max (default 100) — range of the progress values.
- Position — current value (between Min and Max).
- ShowText — display percentage or custom text.
- Style — choose between pbSolid, pbGradient, pbTexture, etc.
Example property setup in Object Inspector:
- Min = 0
- Max = 100
- Position = 0
- ShowText = True
- TextAlignment = taCenter
2) Updating progress from synchronous operations (bad for UI)
If you run a long operation directly on the main thread, the UI will freeze and the progress bar might not repaint. Example of the naive (not recommended) approach:
procedure TForm1.ButtonStartClick(Sender: TObject); var i: Integer; begin AdvProgressBar1.Position := 0; for i := 1 to 100 do begin Sleep(50); // simulate work AdvProgressBar1.Position := i; Application.ProcessMessages; // forces UI update (works but is discouraged) end; end;
Why not to use Application.ProcessMessages:
- Can cause reentrancy bugs (button clicked again, UI events processed unexpectedly).
- Not responsive under heavy loads and blocks input handlers.
3) Recommended: Use background tasks and synchronize UI updates
Preferred approach is to run heavy work off the main thread and marshal progress updates back to the UI. Examples below use TTask (System.Threading) and TThread.Synchronize/TThread.Queue.
Example using TTask and TThread.Queue:
uses System.Threading, System.Classes; procedure TForm1.ButtonStartClick(Sender: TObject); begin AdvProgressBar1.Position := AdvProgressBar1.Min; AdvProgressBar1.Max := 100; TTask.Run( procedure var i: Integer; begin for i := 1 to 100 do begin // Simulate work Sleep(50); // Queue update to main thread (non-blocking) TThread.Queue(nil, procedure begin AdvProgressBar1.Position := i; end ); end; end ); end;
Notes:
- TThread.Queue is preferred over Synchronize because it doesn’t block the worker thread.
- Use TTask.Run for easier task management; cancelation tokens can be added for responsive cancellation.
4) Reporting progress via events or interfaces
For structured code, expose progress via callbacks or TProgress
Example with TProgress
uses System.Threading, System.SysUtils, System.Classes; procedure TForm1.ButtonStartClick(Sender: TObject); var Progress: IProgress<Integer>; begin AdvProgressBar1.Position := AdvProgressBar1.Min; Progress := TProgress<Integer>.Create( procedure(Value: Integer) begin AdvProgressBar1.Position := Value; end ); TTask.Run( procedure var i: Integer; begin for i := 1 to 100 do begin Sleep(50); (Progress as IProgress<Integer>).Report(i); end; end ); end;
Benefits:
- Decouples UI from worker logic.
- Easier to unit-test worker code.
- Integrates with libraries that accept IProgress
.
5) Indeterminate and marquee modes
When you cannot determine progress (unknown total time), use an indeterminate or animated mode.
- Set Style to an indeterminate variant (check TAdvProgressBar’s Style or Mode property).
- Use built-in animation or periodic timer to change Position smoothly.
Example using a timer to animate when work is indeterminate:
procedure TForm1.StartIndeterminate; begin AdvProgressBar1.Min := 0; AdvProgressBar1.Max := 100; Timer1.Interval := 50; Timer1.Enabled := True; end; procedure TForm1.Timer1Timer(Sender: TObject); begin AdvProgressBar1.Position := (AdvProgressBar1.Position + 3) mod AdvProgressBar1.Max; end;
Stop the timer when the operation completes and set Position to Max or hide the bar.
6) Smooth animation and visual polish
- Use gradient styles and set SmoothStep or AnimationSpeed properties if available.
- Show custom text: AdvProgressBar1.Text := Format(‘Processing %d%%’, [Percent]); or use ShowText with a CustomText callback.
- Overlay an icon or label for additional context (e.g., “Downloading 42 MB of 200 MB”).
- For tasks with subtasks, use stacked or segmented bars (if supported) or multiple TAdvProgressBar controls.
7) Cancelation and error handling
- Provide a Cancel button that signals a worker thread to stop. Use a TAtomic
or TTask’s cancellation token.
Example with a simple volatile flag:
type TForm1 = class(TForm) CancelButton: TButton; private FCancelRequested: Boolean; end; procedure TForm1.ButtonCancelClick(Sender: TObject); begin FCancelRequested := True; end; TTask.Run( procedure var i: Integer; begin for i := 1 to 100 do begin if FCancelRequested then Exit; Sleep(50); TThread.Queue(nil, procedure begin AdvProgressBar1.Position := i; end); end; end );
- Handle exceptions in worker threads and report errors to the UI via TThread.Queue.
8) Performance considerations
- Avoid very frequent UI updates; batch updates (e.g., update every Nth iteration or throttle to 25–60 FPS).
- Use TThread.Queue for non-blocking UI updates; TThread.Synchronize blocks the worker thread.
- For massive work, measure where time is spent and update only meaningful progress increments.
9) Accessibility and UX tips
- Provide textual progress (percent or bytes) for screen readers.
- Announce large progress jumps or completion.
- Use colors and contrast that meet accessibility standards.
- Offer an estimated time remaining if you can estimate based on average throughput.
10) Troubleshooting common issues
- Bar not updating: ensure updates occur on the main thread (use Queue/Synchronize).
- UI freezes: worker still on main thread — move heavy work to TTask/TThread.
- Flicker: disable unnecessary repaints, use double-buffering if available.
- Incorrect range: confirm Min/Max values reflect the task size.
Example: File copy with progress and cancellation
Full example combining TTask, IProgress, and cancellation:
uses System.Classes, System.SysUtils, System.Threading; procedure TForm1.ButtonCopyClick(Sender: TObject); var Progress: IProgress<Integer>; CancelFlag: TAtomic<Boolean>; begin CancelFlag := TAtomic<Boolean>.Create(False); Progress := TProgress<Integer>.Create( procedure(Value: Integer) begin AdvProgressBar1.Position := Value; LabelStatus.Caption := Format('%d%%', [Value]); end ); TTask.Run( procedure var Src, Dest: TFileStream; Buffer: TBytes; TotalSize, ReadBytes, Processed: Int64; Percent: Integer; begin try Src := TFileStream.Create('C:igfile.bin', fmOpenRead or fmShareDenyNone); try TotalSize := Src.Size; Dest := TFileStream.Create('C:py.bin', fmCreate); try SetLength(Buffer, 65536); Processed := 0; while Processed < TotalSize do begin if CancelFlag.Value then Exit; ReadBytes := Src.Read(Buffer[0], Length(Buffer)); if ReadBytes = 0 then Break; Dest.Write(Buffer[0], ReadBytes); Inc(Processed, ReadBytes); Percent := Round((Processed / TotalSize) * 100); (Progress as IProgress<Integer>).Report(Percent); end; finally Dest.Free; end; finally Src.Free; end; except on E: Exception do TThread.Queue(nil, procedure begin ShowMessage('Copy failed: ' + E.Message); end); end; end ); end; procedure TForm1.ButtonCancelClick(Sender: TObject); begin CancelFlag.Value := True; end;
Summary (quick checklist)
- Run heavy work off the main thread (TTask/TThread).
- Marshal UI updates with TThread.Queue or IProgress
. - Use indeterminate mode for unknown durations.
- Throttle updates and allow cancelation.
- Style the bar and present textual context for better UX.
If you want, I can convert the examples to a specific Delphi version, add visuals/screenshots, or provide ready-to-drop-in unit code for a sample app.
Leave a Reply