BarGraphControl
Horizontal bar graph for showing a single value against a maximum — CPU load, memory use, disk capacity, progress toward a target.
Overview
BarGraphControl renders one labelled horizontal bar: an optional label, a filled/unfilled bar
track, and an optional formatted value. It is a display control — it takes no focus, handles no
input, and simply reflects whatever you assign to Value.
Colors can be static, driven by thresholds (green below 50, yellow below 80, red above), or
interpolated across a smooth gradient. Setting Value repaints automatically — the framework is
reactive at the property boundary, so you never call Invalidate yourself. Enable AnimateValue and
the bar eases to its new value instead of snapping, while the numeric readout still jumps to the
target immediately.
For a series of values over time use SparklineControl; for indeterminate or step-based work use ProgressBarControl.
Quick Start
var bar = Controls.BarGraph()
.WithLabel("CPU")
.WithValue(42)
.WithMaxValue(100)
.WithStandardGradient()
.Build();
window.AddControl(bar);
// Later — assigning Value repaints on its own
bar.Value = 78;
Builder API
Create a builder with Controls.BarGraph() (or new BarGraphBuilder()).
Value and scale
.WithValue(double value) // Current value (default 0)
.WithMaxValue(double maxValue) // Full-scale value (default 100)
.WithAnimatedValue(bool animate = true, TimeSpan? duration = null)
Label and readout
.WithLabel(string label) // Text shown before the bar
.WithLabelWidth(int width) // Fixed label column width (aligns several bars)
.WithLabelSeparator(string separator) // Between label and bar (default ": ")
.ShowLabel(bool show = true)
.ShowValue(bool show = true)
.WithValueFormat(string format) // Numeric format string (default "F1")
Size and layout
.WithBarWidth(int width) // Bar track width in cells (default 20)
.WithWidth(int width)
.WithAlignment(HorizontalAlignment alignment)
.WithVerticalAlignment(VerticalAlignment alignment)
.WithMargin(int left, int top, int right, int bottom)
.WithMargin(Margin margin)
.Visible(bool visible)
.WithName(string name)
Colors
.WithFilledColor(Color color) // Filled portion
.WithUnfilledColor(Color color) // Track behind the bar (default Grey35)
.WithColors(Color filled, Color unfilled)
.WithBackgroundColor(Color color)
.WithForegroundColor(Color color)
.WithColorRole(ColorRole role, ThemeMode? mode = null)
.Outline(bool outline = true)
Threshold and gradient coloring
.WithGradient(params ColorThreshold[] thresholds) // Explicit thresholds
.WithStandardGradient() // 0 green, 50 yellow, 80 red
.WithSmoothGradient(ColorGradient gradient)
.WithSmoothGradient(string gradientSpec) // e.g. "cool", "blue→cyan→green"
.WithSmoothGradient(params Color[] colors)
ColorThreshold is a record struct (double Threshold, Color Color). The bar takes the color of the
highest threshold its value has reached.
Precedence: threshold gradients (
WithGradient/WithStandardGradient) win over smooth gradients when both are set.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
Value |
double |
0 |
Current value. Assigning repaints the bar |
MaxValue |
double |
100.0 |
Value corresponding to a completely filled bar |
Label |
string |
"" |
Label text shown before the bar |
LabelWidth |
int? |
null |
Fixed label column width; null sizes to content |
LabelSeparator |
string |
": " |
Text between the label and the bar |
ShowLabel |
bool |
true |
Whether the label is rendered |
ShowValue |
bool |
true |
Whether the numeric value is rendered |
ValueFormat |
string |
"F1" |
.NET numeric format string for the value |
BarWidth |
int |
20 |
Width of the bar track in cells |
FilledColor |
Color |
theme | Color of the filled portion |
UnfilledColor |
Color |
Grey35 |
Color of the empty track |
BackgroundColor |
Color? |
null |
Control background; inherits from container when null |
ForegroundColor |
Color? |
null |
Text color; inherits from container when null |
ColorRole |
ColorRole |
Default |
Semantic color role (see Themes) |
ColorRoleMode |
ThemeMode? |
null |
Optional theme mode override for the role |
Outline |
bool |
false |
Outline styling — role color on text and border |
ColorThresholds |
IReadOnlyList<ColorThreshold>? |
null |
Threshold-based colors |
SmoothGradient |
ColorGradient? |
null |
Gradient interpolated across the fill |
AnimateValue |
bool |
false |
Ease the bar to new values instead of snapping |
AnimationDuration |
TimeSpan |
250 ms |
Duration of a value transition when animating |
The bar is drawn with █ for the filled portion and ░ for the remainder.
Events
BarGraphControl raises no control-specific events. It implements INotifyPropertyChanged through
BaseControl, so its properties can be data-bound — see Data Binding.
Keyboard and mouse support
None. BarGraphControl is a display-only control: it is not focusable and does not process keyboard
or mouse input. To make a bar interactive, pair it with a focusable control such as
SliderControl.
Examples
Several aligned bars
Give every bar the same LabelWidth so the tracks line up in a column.
static BarGraphControl MakeBar(string name, string label, double value) =>
Controls.BarGraph()
.WithName(name)
.WithLabel(label)
.WithLabelWidth(8)
.WithValue(value)
.WithMaxValue(100)
.WithValueFormat("F0")
.WithStandardGradient()
.WithAlignment(HorizontalAlignment.Stretch)
.Build();
window.AddControl(MakeBar("cpuBar", "CPU", 25));
window.AddControl(MakeBar("memBar", "Memory", 45));
window.AddControl(MakeBar("diskBar", "Disk", 60));
Live updates from a window thread
Look the control up by name and assign Value. The assignment invalidates the control for you.
var window = new WindowBuilder(windowSystem)
.WithTitle("System Monitor")
.WithAsyncWindowThread(async (win, ct) =>
{
while (!ct.IsCancellationRequested)
{
var bar = win.FindControl<BarGraphControl>("cpuBar");
if (bar != null)
bar.Value = ReadCpuPercent();
await Task.Delay(500, ct);
}
})
.BuildAndShow();
Thread safety: assigning
Valuefrom a background thread is safe because it only invalidates. Mutating window state (adding/removing controls, changing the title) from off the UI thread must go throughwindowSystem.EnqueueOnUIThread(...)— see Threading & Async.
Custom thresholds
Bars that are "good when high" — like remaining battery — want the opposite of the standard ramp.
var battery = Controls.BarGraph()
.WithLabel("Battery")
.WithValue(18)
.WithGradient(
new ColorThreshold(0, Color.Red),
new ColorThreshold(20, Color.Yellow),
new ColorThreshold(50, Color.Green))
.Build();
Smooth gradient
// From a spec string — predefined names, or arrow notation
var load = Controls.BarGraph()
.WithLabel("Load")
.WithValue(72)
.WithSmoothGradient("blue→cyan→green")
.Build();
// Or from explicit colors
var thermal = Controls.BarGraph()
.WithLabel("Temp")
.WithValue(64)
.WithSmoothGradient(Color.Green, Color.Yellow, Color.Red)
.Build();
Animated transitions
var bar = Controls.BarGraph()
.WithLabel("Throughput")
.WithMaxValue(1000)
.WithAnimatedValue(true, TimeSpan.FromMilliseconds(400))
.WithSmoothGradient("cool")
.Build();
bar.Value = 750; // the bar eases over 400 ms; the readout shows 750.0 at once
Non-percentage scales
MaxValue and ValueFormat are independent, so any unit works.
var disk = Controls.BarGraph()
.WithLabel("Disk")
.WithValue(412.5)
.WithMaxValue(1024)
.WithValueFormat("F1")
.WithLabelSeparator(" ")
.Build();
Best Practices
- Give related bars the same
LabelWidthso their tracks align into a readable column. - Set
MaxValueto the real full-scale value rather than pre-scaling inputs to 0–100 — the control does the arithmetic, and the raw value stays readable in the readout. - Use
WithValueFormat("F0")for percentages. The default"F1"adds a decimal place that rarely helps for whole-percent readings. - Pick thresholds or a smooth gradient, not both — thresholds silently win. Thresholds suit alert-style bars with meaningful boundaries; smooth gradients suit continuous readings.
- Reach for
AnimateValueon slow-polling bars. When updates arrive every few seconds, easing reads as far less jumpy; leave it off for fast-refreshing bars where it just adds lag. - Assign
Valueand stop there. CallingContainer?.Invalidate()yourself is redundant — the setter already invalidates. - Don't reach for it as a progress indicator. ProgressBarControl
handles indeterminate and step-based work;
BarGraphControlis for measured quantities.
See Also
- SparklineControl — a value series over time
- LineGraphControl — multi-series line plots
- ProgressBarControl — task progress, including indeterminate
- SliderControl — an interactive value the user can change
- Themes — color roles and theme-aware coloring
- Gradients — gradient specification syntax