← Back to Home • Next: ProgressBar — Styles →
How the ProgressBar control behaves while it runs: the update handler contract, the
ProgressBarEvent you drive it with, input/output context, cancellation, and errors.
Wait Progress: 42 % ← prompt + answer (value)
Processed: 42% ← ChangeDescription output (optional)
0 [██████████░░░░░░░░░░░░░░] 100 ⠹ ← range + slider + spinner
00:00:03 ← elapsed time
Esc: cancel ← tooltip
Each element can be hidden (HideElements) and recolored
(Styles). Unlike an interactive control, ProgressBar has no editing keys — but it does
still respond to F1 (cycle tooltip content) and Ctrl+F1 (show/hide the tooltip), same as
every other control. Esc aborts (when the abort key is enabled). Everything else is driven by
your update handler.
The heart of the control is a single loop you register with
UpdateHandler or UpdateHandlerAsync.
The control renders on its own thread; your handler runs the work and reports progress. A well-formed
loop looks like this:
.UpdateHandler((bar, token) =>
{
while (!token.IsCancellationRequested && !bar.Finish)
{
// do a slice of work…
token.WaitHandle.WaitOne(80);
// …then report the new value
bar.Update(bar.Value + 2);
}
})
The two loop conditions matter:
!token.IsCancellationRequested — exit when the run is cancelled (Esc or the Run(token)
token). This keeps the UI responsive.!bar.Finish — exit when the value has reached the maximum (or the handler aborted). Finish
flips to true automatically once bar.Value >= bar.Maxvalue.When the handler returns, the control closes and returns the final
StateProgress.
Final frame is always rendered. When the loop ends (completion or cancellation), the control paints one last frame before closing, so the finished bar, percentage and the
ElapsedTimereflect the actual end state — the elapsed value shown matchesStateProgress.ElapsedTimeand is not frozen a frame early. If you setHideAfterFinish(true)the frame is erased instead.
ProgressBarEventYour handler receives a ProgressBarEvent (bar above). It is the mutable bridge between your work
and the rendered bar.
| Member | Purpose |
|---|---|
Value |
The current value |
Minvalue / Maxvalue |
The configured range bounds |
Update(double value) |
Sets the current value, clamped to [Minvalue, Maxvalue] |
Finish |
true when aborted or Value >= Maxvalue |
HasChange() |
true if the value changed (or aborted) since the last check |
ErrorAndAbort(Exception?) |
Records an error and aborts the run |
Error |
The recorded exception, if any |
InputParam<T>(key, out found) |
Reads a value from the input context |
AddOutputContext<T>(key, value) |
Writes a value to the output context |
RemoveOutputContext(key) |
Removes an output-context entry |
OutputContext |
The accumulated output context (read-only) |
💡
Updateclamps for you, sobar.Update(bar.Value + step)can safely overshoot the maximum on the last iteration — the value is capped atMaxvalueandFinishbecomestrue.
Both handler overloads accept an optional context dictionary. Values you put there are readable
inside the loop via InputParam<T>; values you write with AddOutputContext are exposed afterwards
on StateProgress.OutputContext (and via GetOutput<T>).
var result = PromptPlus.Controls.ProgressBar("Wait Progress: ")
.UpdateHandler((bar, token) =>
{
int step = bar.InputParam<int>("step", out bool hasStep);
if (!hasStep) step = 1;
string tag = bar.InputParam<string>("tag", out _);
while (!token.IsCancellationRequested && !bar.Finish)
{
token.WaitHandle.WaitOne(90);
bar.Update(bar.Value + step);
bar.AddOutputContext("LastTag", tag);
}
bar.AddOutputContext("FinishedAt", DateTimeOffset.UtcNow.ToString("O"));
},
new Dictionary<string, object?> { ["step"] = 4, ["tag"] = "context-sample" })
.Run();
// Read outputs back:
var finishedAt = result.Content.GetOutput<string>("FinishedAt", out bool found);
The input and output dictionaries are independent — writing to the output never mutates the input.
There are two ways the run ends early, both surfacing as IsAborted == true:
Run(token) token is cancelled. Pass your own CancellationToken to enforce a deadline:using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(900)))
{
var result = PromptPlus.Controls.ProgressBar("Wait Progress: ", "Token cancels before finish")
.UpdateHandler(Work)
.Run(cts.Token);
// result.IsAborted == true if the token fired before the bar finished
}
Because your loop tests token.IsCancellationRequested, it exits cleanly; the control then returns
with whatever value it had reached.
To fail the operation from inside the handler, call ErrorAndAbort with an exception. This stops the
run, sets IsAborted, and stores the exception on StateProgress.ExceptionProgress.
.UpdateHandler((bar, token) =>
{
while (!token.IsCancellationRequested && !bar.Finish)
{
token.WaitHandle.WaitOne(100);
if (bar.Value >= 40)
{
bar.ErrorAndAbort(new InvalidOperationException("Simulated failure after 40%."));
return;
}
bar.Update(bar.Value + 5);
}
})
// …later:
if (result.Content.ExceptionProgress is not null)
PromptPlus.Console.WriteLine($"Error: {result.Content.ExceptionProgress.Message}");
Spinner(SpinnersType) shows a looping animation alongside the bar while it
runs. SpinnersType spans several families — braille/dots, lines/bars, shapes, toggles,
arrows/motion, and emoji. On a terminal without Unicode support it automatically falls back to the
Ascii spinner (- \ | /).
See Spinners for usage and the fallback rules, and the Spinner catalog for every spinner’s frames.
ChangeDescriptionAsync is awaited synchronously each time
the value changes. It does not run in parallel with the render loop, so keep the callback fast — a
slow await stalls the refresh.
Set per instance via Options(...), or globally on
PromptPlus.Config:
| Option | Effect on ProgressBar |
|---|---|
EnabledAbortKey(false) |
Removes Esc — the run can only end by finishing or by the Run token |
HideAfterFinish(true) |
Erases the bar after it completes |
ShowTooltip(false) |
Hides the keyboard-hint line |
Prompt(...) / Description(...) |
Overrides the prompt / description text |
Note that HideElements(HideProgressBar.ProgressbarAtFinish) is the
element-level way to remove just the bar at the end, independent of HideAfterFinish.
UpdateHandler or UpdateHandlerAsync, not both.Finish and the token is never cancelled,
the control never returns. Always gate on both conditions.Update clamps. Values outside the range are pinned to the bounds; they never throw.Default and initial value must lie within the configured Range, or construction throws.ProgressBarStyles regionsOptions