BeepNumericUpDown
Themable numeric input with 20+ visual styles, input masking, percentage/currency modes, and 13-event validation pipeline
Table of Contents
Overview
Namespace: TheTechIdea.Beep.Winform.Controls.Numerics
Assembly: TheTechIdea.Beep.Winform.Controls
Inheritance: BeepNumericUpDown → BaseControl → UserControl
Implements: IBeepComponent
BeepNumericUpDown is a feature-rich numeric input control for business applications. It
combines a themable spin-button interface with 20+ NumericStyle presets, four display modes
(Standard, Percentage, Currency, ProgressValue), 30+ input mask presets (phone, SSN, credit card, ISBN,
GPS coordinates, etc.), and a comprehensive multi-stage validation pipeline with 13 dedicated events.
It uses a pluggable painter system for layout and rendering, and supports both inline TextBox editing and mouse/keyboard increment/decrement with auto-repeat acceleration. The control is split across five partial class files for maintainability.
Key Features
- 20+ Numeric Styles: Standard, CompactStepper, Slider, Currency, Percentage, Phone, Decimal, Integer, Rating, Progress, Time, Scientific, Dial, VerticalStepper, Calculator, Temperature, Inline, TouchOptimized, Display, Chip.
- Pluggable Painter System:
INumericUpDownPainterimplementations (StandardNumericPainter, CompactStepperPainter, CurrencyPainter, PhoneNumberPainter, InlineStepperPainter) with hit-area management and layout. - Four Display Modes: Standard, Percentage (auto %), Currency (auto $+decimals), CustomUnit (prefix+suffix+unit), ProgressValue (% with min/max/increment presets).
- 30+ Mask Presets: PhoneUS, PhoneInternational, SSN, CreditCard, ZipCode/Plus4, Currency, Percentage, Time24/12Hour, Date formats, IPAddress, MACAddress, Decimal2/4Places, IntegerWithCommas, AccountNumber, RoutingNumber, EIN, ISBN10/13, ProductCode, Latitude, Longitude, WeightKg/Lbs, TemperatureC/F, DistanceKm/Miles, FileSizeMB/GB, Scientific, Hex, Binary, Custom.
- 13-Event Validation Pipeline: ValueValidating (basic + extended), ValueValidated, RangeExceeded, InvalidInput, FormatValidation, StepValidationFailed, MinimumReached, MaximumReached, plus per-char and per-value comprehensive helpers.
- Keyboard Navigation: Arrow Up/Down, PageUp/PageDown (10×step), Home/End (min/max), Enter/Escape commit/cancel, mouse wheel, configurable arrow-key interception.
- Auto-Repeat Acceleration: 500ms initial delay, 50ms repeat, accelerates after 5 repeats. Direction tracked via _repeatIncrementDirection.
- Inline TextBox Editing: Transparent overlay for direct typing with SelectAllOnFocus, invalid-input highlighting (red background), and prefix/suffix stripping on commit.
- Percentage Auto-Calc: SetPercentageValue(part, total), GetActualValue() computes from TotalValue when AutoCalculateFromTotal is true.
- Value Wrapping: WrapValues wraps max→min and min→max on increment/decrement.
- Accessibility: AccessibleRole=SpinButton, configurable AccessibleName/Description.
Display Modes (NumericUpDownDisplayMode)
| Mode | Description | Auto-Config |
|---|---|---|
Standard | Plain numeric with optional prefix/suffix. | No auto-formatting. |
Percentage | 0-100 range with %. | Suffix="%", Decimals=0, range 0-100. |
Currency | Monetary with symbol. | Prefix="$", Decimals=2, ThousandsSep=true. |
CustomUnit | Custom prefix/suffix/unit. | Format: {Prefix}{Value}{Suffix} {Unit}. |
ProgressValue | Progress percentage. | Suffix="%", min=0, max=100, Decimals=0, Increment=1. |
Numeric Styles (NumericStyle Enum — 22 values)
| Style | Painter | Auto-Applied Settings |
|---|---|---|
Standard | StandardNumericPainter | Default side-button layout. |
CompactStepper | CompactStepperPainter | Compact right-side only buttons. |
Slider | (Standard) | Spin buttons hidden. Future: draggable slider. |
Currency | CurrencyPainter | Mode=Currency, Decimals=2, Thousands=true. |
Percentage | CompactStepperPainter | Mode=Percentage, min=0, max=100, Decimals=1. |
Phone | PhoneNumberPainter | Decimals=0, AllowNegative=false, min=0. |
Decimal | CompactStepperPainter | DecimalPlaces=2. |
Integer | CompactStepperPainter | DecimalPlaces=0. |
Rating | (Standard) | min=0, max=5, Increment=1, Decimals=0. |
Progress | (Standard) | min=0, max=100, Mode=Percentage, ReadOnly=true. |
Time | (Standard) | Future: time input. |
Scientific | (Standard) | DecimalPlaces=4. |
Dial | (Standard) | Buttons hidden. Future: circular dial. |
VerticalStepper | StandardNumericPainter | Vertical button layout. |
Calculator | (Standard) | Future: calculator-style pad. |
Temperature | (Standard) | Decimals=1, Suffix="°C". |
Inline | InlineStepperPainter | No buttons; inline edit only. |
TouchOptimized | (Standard) | Future: large touch buttons. |
Display | (Standard) | Read-only; no buttons. |
Chip | (Standard) | Future: chip/tag style. |
Mask Presets (NumericMaskPreset — 37 values)
| Preset | Pattern | MaxLen | Decimals | Icon |
|---|---|---|---|---|
None | — | 50 | — | — |
PhoneUS | (###) ###-#### | 14 | 0 | Phone |
PhoneInternational | +## (###) ###-#### | 18 | 0 | Phone |
SSN | ###-##-#### | 11 | 0 | Id |
CreditCard | #### #### #### #### | 19 | 0 | CreditCard |
ZipCode | ##### | 5 | 0 | MapPin |
ZipCodePlus4 | #####-#### | 10 | 0 | MapPin |
Currency | $#,###.## | 50 | 2 | CashBanknote |
Percentage | ##.##% | 50 | 2 | Percentage |
Time24Hour | ##:## | 5 | 0 | Clock |
Time12Hour | ##:## AM | 8 | 0 | Clock |
DateMMDDYYYY | ##/##/#### | 10 | 0 | Calendar |
DateYYYYMMDD | ####-##-## | 10 | 0 | Calendar |
IPAddress | ###.###.###.### | 15 | 0 | Globe |
MACAddress | ##:##:##:##:##:## | 17 | 0 | Hash |
Decimal2Places | #,###.## | 50 | 2 | Hash |
Decimal4Places | #,###.#### | 50 | 4 | Hash |
IntegerWithCommas | #,### | 50 | 0 | Hash |
AccountNumber | ####-####-#### | 14 | 0 | CreditCard |
RoutingNumber | ######### | 9 | 0 | BldgTower |
EIN | ##-####### | 10 | 0 | BldgTower |
ISBN10 | #-###-#####-# | 13 | 0 | Hash |
ISBN13 | ###-#-###-#####-# | 17 | 0 | Hash |
ProductCode | ###-###-### | 11 | 0 | Package |
Latitude | ##.###### | 50 | 6 | MapPin |
Longitude | ###.###### | 50 | 6 | MapPin |
WeightKg | #,###.## | 50 | 2 | Weight |
WeightLbs | #,###.## | 50 | 2 | Weight |
TemperatureCelsius | ###.# | 50 | 1 | Temperature |
TemperatureFahrenheit | ###.# | 50 | 1 | Temperature |
DistanceKm | #,###.## | 50 | 2 | Navigation |
DistanceMiles | #,###.## | 50 | 2 | Navigation |
FileSizeMB | #,###.## | 50 | 2 | Desktop |
FileSizeGB | #,###.## | 50 | 2 | Desktop |
Scientific | #.####E+## | 50 | 4 | AtomN2 |
Hexadecimal | 0x######## | 10 | 0 | Hash |
Binary | 0b######## | 10 | 0 | Hash |
Use Custom with CustomMask for user-defined patterns (e.g. "####-####").
Properties (29 total)
| Property | Type | Default | Category | Description |
|---|---|---|---|---|
MinimumValue | decimal | 0m | Numeric Settings | Minimum allowed value. Clamps Value if set below. |
MaximumValue | decimal | 100m | Numeric Settings | Maximum allowed value. Clamps Value if set above. |
IncrementValue | decimal | 1m | Numeric Settings | Step for spin buttons, arrow keys, wheel. |
Value | decimal | 0m | Numeric Settings | Current value. Triggers validate + ValueValidating + ValueChanged + boundary events. |
DecimalPlaces | int | 0 | Format | Decimal places (0-15). Affects display formatting and input validation. |
DisplayMode | NumericUpDownDisplayMode | Standard | Format | Display: Standard, Percentage, Currency, CustomUnit, ProgressValue. Auto-configures prefix/suffix. |
NumericStyle | NumericStyle | Standard | Appearance | Visual/interaction style. Triggers ConfigureForNumericStyle() and InitializePainter(). |
Prefix | string | "" | Format | String prepended to displayed value (e.g. "$", "£"). |
Suffix | string | "" | Format | String appended to displayed value (e.g. "%", "kg"). |
Unit | string | "" | Format | Unit label in CustomUnit mode. |
ThousandsSeparator | bool | false | Format | Display thousands commas (e.g. 1,234,567). |
ReadOnly | bool | false | Behavior | Prevent user changes. Sets TextBox.ReadOnly on overlay. |
InterceptArrowKeys | bool | true | Behavior | Up/Down adjust value vs. navigate focus. |
AllowNegative | bool | true | Behavior | Prohibit negatives. Forces MinimumValue to 0. |
UseThemeColors | bool | true | Appearance | Derive colours from theme via NumericThemeHelpers. |
WrapValues | bool | false | Behavior | Increment past max wraps to min; decrement past min wraps to max. |
SelectAllOnFocus | bool | true | Behavior | Select entire text on GotFocus. |
ButtonSize | NumericSpinButtonSize | Standard | Appearance | Small (20), Standard (24), Large (28), ExtraLarge (32). |
ShowSpinButtons | bool | true | Appearance | Hide buttons for inline-only editing. |
IsPercentageMode | bool | false | Business | Shortcut: Mode=Percentage, min=0, max=100, Decimals=0. |
AutoCalculateFromTotal | bool | false | Business | GetActualValue() = Value/100 * TotalValue when true in % mode. |
EnableShadow | bool | false | Appearance | Subtle shadow around control. |
AccessibilityName | string | "" | Accessibility | Screen-reader name. Maps to AccessibleName. |
AccessibilityDescription | string | "" | Accessibility | Input guidance. Maps to AccessibleDescription. |
TotalValue | decimal | 0m | Business | Total used when AutoCalculateFromTotal=true. |
MaskPreset | NumericMaskPreset | None | Format | Predefined mask. Triggers ApplyMaskPreset(). |
CustomMask | string | "" | Format | Custom pattern when MaskPreset=Custom. # for digits. |
MaskConfig | NumericMaskConfig? | null | — | Read-only resolved mask config. |
CurrentPainter | INumericUpDownPainter | (resolved) | — | Read-only active painter. |
Methods
| Method | Visibility | Signature | Description |
|---|---|---|---|
PerformStep | public | void PerformStep() | Increments Value by IncrementValue. |
ConfigureForPercentage | public | void ConfigureForPercentage() | Mode=ProgressValue, IsPercentageMode=true. |
ConfigureForCurrency | public | void ConfigureForCurrency(string symbol="$") | Mode=Currency, Prefix, Decimals=2, Thousands=true. |
ConfigureForTaskProgress | public | void ConfigureForTaskProgress() | Mode=ProgressValue, min=0, max=100, Increment=5, PercentageMode. |
SetPercentageValue | public | void SetPercentageValue(decimal part, decimal total) | Sets Value = Math.Round(part/total*100). |
GetActualValue | public | decimal GetActualValue() | % mode: Value/100*TotalValue. Else: Value. |
SetValue | public | void SetValue(object value) | Sets Value from decimal or string. IBeepComponent. |
GetValue | public | object GetValue() | Returns Value as object. |
ApplyTheme | public | void ApplyTheme() | Applies Back/Fore/Border/Hover colours, TextBox theme, error colour via NumericThemeHelpers. |
GetMaskIconPath | public | string GetMaskIconPath() | Returns icon path from current mask config. |
ShouldShowMaskIcon | public | bool ShouldShowMaskIcon() | True when mask config has ShowIcon=true and non-empty IconPath. |
ValidateRange | public | bool ValidateRange(decimal, out string) | Check value ∈ [Min, Max]. Raises RangeExceeded on failure. |
ValidateFormat | public | bool ValidateFormat(string, out string) | Check input parses as decimal and matches mask. Raises InvalidInput/FormatValidation. |
ValidateStep | public | bool ValidateStep(decimal, out string) | Check (value-Min) % Increment == 0. Raises StepValidationFailed. |
ValidateNumericInput | public | bool ValidateNumericInput(char, string, int) | Per-char: reject non-digits, dup decimals, misplaced minus. |
ValidateDecimalPlaces | public | bool ValidateDecimalPlaces(string, out string) | Check decimal places ≤ DecimalPlaces. Raises FormatValidation. |
ValidateValue | public | bool ValidateValue(decimal, out string) | Full validation: range+step+ValueValidating+ValueValidatingEx+ValueValidated. |
Events (13 total)
| Event | Args Type | Fire Point | Description |
|---|---|---|---|
ValueChanged | EventHandler | After range clamp, before boundary checks | Value property changed. |
ValueValidating | ValueValidatingEventArgs | Before Value change committed | Cancel via e.Cancel. OldValue/NewValue provided. |
MinimumReached | EventHandler | Value == MinimumValue after change | Floor boundary hit. |
MaximumReached | EventHandler | Value == MaximumValue after change | Ceiling boundary hit. |
UpButtonClicked | EventHandler | IncrementValueInternal called | Up/increment action triggered. |
DownButtonClicked | EventHandler | DecrementValueInternal called | Down/decrement action triggered. |
ValueValidationFailed | EventHandler | ValueValidating cancelled | Basic validation rejected. |
ValueValidatingEx | NumericValueValidatingEventArgs | Extended validation | OldValue, NewValue, Cancel, ValidationMessage. |
ValueValidated | NumericValueValidatedEventArgs | After validation passes | OldValue, NewValue, IsValid, ValidationMessage. |
RangeExceeded | RangeExceededEventArgs | ValidateRange fails | AttemptedValue, Min/Max, IsMaximumExceeded. |
InvalidInput | InvalidInputEventArgs | Parse/mask validation fails | Input string and Reason. |
FormatValidation | FormatValidationEventArgs | Format check occurs | Input, IsValid, ExpectedFormat. |
StepValidationFailed | EventArgs | ValidateStep fails | Value not a multiple of IncrementValue. |
Validation Pipeline
The Value setter executes the following validation sequence:
- Range Clamp:
ValidateValueRange(newValue)— clamps or wraps value to [Min, Max]. - Equality Check: Skip if old == new.
- Basic Validation:
ValueValidating?.Invoke(this, args)— user can cancel. On cancel:ValueValidationFailedfires, setter returns. - Value Commit:
_value = newValue. - Change Notification:
ValueChangedfires. - Boundary Events:
MinimumReached/MaximumReachedif at boundary. - Invalidation:
Invalidate()triggers repaint.
Programmatic validation is available through public methods: ValidateRange,
ValidateFormat, ValidateStep, ValidateNumericInput,
ValidateDecimalPlaces, and the comprehensive ValidateValue which
chains range+step+ValueValidating+ValueValidatingEx+ValueValidated.
Theming
BeepNumericUpDown uses dedicated theme helpers under Numerics.Helpers:
NumericThemeHelpers.GetNumericBackgroundColor(theme, useTheme, isHovered)— background.NumericThemeHelpers.GetNumericTextColor(theme, useTheme, isHovered)— foreground.NumericThemeHelpers.GetNumericBorderColor(theme, useTheme, isHovered)— border.NumericThemeHelpers.GetErrorColor(theme, useTheme)— invalid-input background.NumericFontHelpers.GetNumericFont(controlStyle)— font for TextBox overlay.
ApplyTheme() updates BackColor, ForeColor, BorderColor,
HoverBackColor, HoverForeColor, HoverBorderColor, and the
internal TextBox's BackColor, ForeColor, and Font. BeepStyling handles background,
border, and shadow at the BaseControl level.
Examples
Basic Numeric
var n = new BeepNumericUpDown { Min=0, Max=100, Increment=5, Value=50, Decimals=1, Width=150 };
n.ValueChanged += (s, e) => Console.WriteLine($"New: {n.Value}");
Phone with Mask
var phone = new BeepNumericUpDown { MaskPreset = NumericMaskPreset.PhoneUS, NumericStyle = NumericStyle.Phone, Width = 200 };
Currency with Validation
var cur = new BeepNumericUpDown(); cur.ConfigureForCurrency("£");
cur.Min=0; cur.Max=1000000;
cur.ValueValidating += (s, e) => { if (e.NewValue > 50000) e.Cancel = true; };
Percentage Auto-Calc
var pct = new BeepNumericUpDown { IsPercentageMode=true, AutoCalculateFromTotal=true, TotalValue=1000m };
pct.Value = 25; decimal actual = pct.GetActualValue(); // 250.0
Bulk Configuration
new BeepNumericUpDown().ConfigureForPercentage();
new BeepNumericUpDown().ConfigureForTaskProgress();
new BeepNumericUpDown().ConfigureForCurrency("€");
Wrap Values
var w = new BeepNumericUpDown { Min=0, Max=10, WrapValues=true, Value=10 };
w.PerformStep(); // wraps to 0
GPS Latitude
var lat = new BeepNumericUpDown { MaskPreset = NumericMaskPreset.Latitude, Width = 180 };
// Auto: Decimals=6, min=-90, max=90
Architecture
| File | Lines | Responsibility |
|---|---|---|
BeepNumericUpDown.cs | ~722 | Main: fields (29 private), properties (29), events (13), constructor (sets ControlStyles, BorderRadius, initialises), display mode config, ValidateValueRange, public methods (PerformStep, ConfigureFor*, SetPercentageValue, GetActualValue, SetValue, GetValue, ApplyTheme, Dispose), ValueValidatingEventArgs. |
BeepNumericUpDown.Drawing.cs | ~189 | Rendering: DrawContent, Draw, Paint (BeepStyling background+border+painter layout+text+button icons), PaintButtonBackgrounds, GetControlPath, OnResize, CalculateButtonAreas, GetButtonWidthForSize. |
BeepNumericUpDown.Events.cs | ~252 | Input: OnMouseMove (hover tracking), OnMouseLeave, OnMouseDown (button press + repeat start + edit start), OnMouseUp (release + stop repeat), OnMouseClick (hit area), OnMouseDoubleClick (edit start), OnMouseWheel, OnKeyDown (arrows/PgUp/PgDn/Home/End/Enter/Escape), ProcessDialogKey, OnGotFocus (SelectAll), OnLostFocus (EndEditing), OnEnabledChanged, StartRepeatTimer/StopRepeatTimer/RepeatTimer_Tick. |
BeepNumericUpDown.Painters.cs | ~183 | Painters: InitializePainter (maps NumericStyle to painters: CompactStepper, Inline, Currency, Phone, Standard fallback), RefreshHitAreas, UpdateButtonHoverState, HandleHitAreaClick, NumericUpDownPainterContext adapter (exposes Value, Min, Max, Increment, Decimals, state, buttons, layout, display, theme, actions to painters). |
BeepNumericUpDown.Helpers.cs | ~610 | Helpers: IncrementValueInternal/DecrementValueInternal (wrap-aware, fires Up/DownButtonClicked), FormatValue (decimals+thousands+display mode), UpdateDisplayText, ConfigureForNumericStyle (all 22 styles), TextBox management (StartEditing: create+position+focus+selectall, EndEditing: parse+validate+highlight+events), mask preset (ApplyMaskPreset, CreateCustomMaskConfig, GetMaskIconPath, ShouldShowMaskIcon), validation (ValidateRange, ValidateFormat, ValidateStep, ValidateNumericInput, ValidateDecimalPlaces, ValidateValue). |
NumericStyle.cs | ~133 | Enum: 22 NumericStyle values with DescriptionAttributes. |
NumericMaskPreset.cs | ~449 | Enum: 37 mask presets + NumericMaskConfig class with FromPreset factory (MaskPattern, MaxLength, DecimalPlaces, AllowNegative, Min/Max, IconPath, Unit, ShowIcon). |
Painters/INumericUpDownPainter.cs | ~30 | Interface: CalculateLayout, FormatValue, PaintValueText, PaintButtonIcons, UpdateHitAreas. |
Painters/StandardNumericPainter.cs | ~100 | Default: side buttons, standard layout. |
Painters/CompactStepperPainter.cs | ~80 | Compact: right-side only. |
Painters/CurrencyPainter.cs | ~80 | Currency: symbol placement, formatted display. |
Painters/PhoneNumberPainter.cs | ~80 | Phone: formatted display with mask. |
Painters/InlineStepperPainter.cs | ~80 | Inline: no buttons. |
Painters/BaseModernNumericPainter.cs | ~60 | Abstract base for modern-style painters. |
Helpers/NumericThemeHelpers.cs | ~60 | Colour resolution. |
Helpers/NumericFontHelpers.cs | ~30 | Font resolution. |
Helpers/NumericStyleHelpers.cs | ~40 | Style metrics. |
Helpers/NumericIconHelpers.cs | ~30 | Icon defaults. |
Inline TextBox Editing
The control uses a transparent TextBox overlay (_textBox) for direct
numeric entry. Key behaviours:
- Creation: Lazily created in
StartEditing()with BorderStyle=None, theme-derived BackColor/ForeColor, and font from NumericFontHelpers. - Positioning: Centred vertically, inset by padding, accounting for both left and right spin-button widths when visible.
- Focus Behaviour:
SelectAllOnFocusselects all text.OnLostFocuscallsEndEditing(true)to commit. - Commit:
EndEditing(true)strips prefix/suffix/unit symbols and percent signs, parses viadecimal.TryParse. On failure, highlights via_invalidInputColorand firesInvalidInput+ValueValidationFailed. - Cancel:
EndEditing(false)(Escape key) discards the edit and hides the TextBox without changing Value.
Auto-Repeat System
When a spin button is held down, StartRepeatTimer fires an immediate action, then
uses a System.Windows.Forms.Timer with the following progression:
- Initial delay: 500ms (
INITIAL_DELAY) - Repeat interval: 50ms (
REPEAT_DELAY) - Acceleration: After 5 repeats, maintains 50ms interval (can reach 10ms theoretical
MAX_REPEAT_SPEED) - Direction: Tracked via
_repeatIncrementDirection(true=up, false=down) - Cleanup:
StopRepeatTimeron MouseUp, OnEnabledChanged(false), or Dispose