NumericInput.WPF
1.2.1
See the version list below for details.
dotnet add package NumericInput.WPF --version 1.2.1
NuGet\Install-Package NumericInput.WPF -Version 1.2.1
<PackageReference Include="NumericInput.WPF" Version="1.2.1" />
<PackageVersion Include="NumericInput.WPF" Version="1.2.1" />
<PackageReference Include="NumericInput.WPF" />
paket add NumericInput.WPF --version 1.2.1
#r "nuget: NumericInput.WPF, 1.2.1"
#:package NumericInput.WPF@1.2.1
#addin nuget:?package=NumericInput.WPF&version=1.2.1
#tool nuget:?package=NumericInput.WPF&version=1.2.1
NumericInput — WPF Numeric Input Control
A C# / WPF (.NET 8) library for entering and displaying physical numeric values with automatic unit conversion, culture-aware parsing, expression evaluation, and a full set of UX helpers inspired by Gu.Wpf.NumericInput.
Table of Contents
- Packages: Core vs WPF
- Installation
- Solution structure
- Quick start
- Dependency Properties
- NumericBox — inheritable attached properties
- Commands
- Culture support and limitations
- Expression evaluator
- Physics families and units
- Feature reference
- Build and test
- Adding a new unit
- Flowcharts
Packages: Core vs WPF
This repository publishes two separate NuGet packages. Pick the one that matches what you're building:
NumericInput.Core |
NumericInput.WPF |
|
|---|---|---|
| What it is | Platform-agnostic .NET 8 class library | WPF UserControl (net8.0-windows) |
| Contains | UnitRegistry, UnitConverter, ExpressionEvaluator, PhysicsFamily, [UserInterface] attribute |
NumericInputControl, NumericBox, converters, validation/spinner enums |
| Depends on | Nothing (no WPF, no UI framework) | NumericInput.Core (pulled in automatically) |
| Use it when… | You need unit conversion, physical-value parsing, or the expression evaluator in a non-UI project (console app, service, ViewModel-only library, another UI stack) | You're building a WPF app and want the ready-made numeric input control |
In short: NumericInput.Core is the engine (units, conversion, parsing, expression evaluation — no UI at all). NumericInput.WPF is the WPF control built on top of it. Most WPF applications only need to reference NumericInput.WPF — NumericInput.Core comes along transitively via NuGet.
You'd reference NumericInput.Core on its own only if you want the conversion/expression engine without any WPF dependency (e.g. a backend service or a different UI framework).
Installation
# WPF app — installs NumericInput.WPF and NumericInput.Core together
dotnet add package NumericInput.WPF
# Non-UI project — installs only the platform-agnostic engine
dotnet add package NumericInput.Core
<ItemGroup>
<PackageReference Include="NumericInput.WPF" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NumericInput.Core" Version="1.2.1" />
</ItemGroup>
Solution structure
NumericInput.sln
├── NumericInput.Core ← .NET 8 platform-agnostic library
│ ├── PhysicsFamily.cs ← enum of physical families
│ ├── UnitRegistry.cs ← catalogue of 100+ units + lookup
│ ├── UnitConverter.cs ← conversion, parsing "100 bar", formatting
│ ├── ExpressionEvaluator.cs ← arithmetic/trig expression evaluator
│ └── UserInterfaceAttribute.cs ← [UserInterface(…)] + PropertyMetadata
│
├── NumericInput.WPF ← WPF library (.NET 8-windows)
│ ├── NumericInputControl.xaml / .xaml.cs ← main UserControl
│ ├── NumericBox.cs ← inheritable attached properties
│ ├── SpinUpdateMode.cs ← enum (PropertyChanged / AsBinding)
│ ├── ValidationTrigger.cs ← enum (LostFocus / PropertyChanged)
│ └── Converters/ValueConverters.cs
│
├── NumericInput.Example ← sample WPF app (PressureVessel)
│
├── NumericInput.Tests ← xUnit test suite (788 tests)
│ ├── NumericInputTests.cs
│ ├── CultureParseTests.cs ← 73 culture tests
│ └── CultureExtendedTests.cs ← 413 tests · 32 cultures
└── NumericInput.WPF.Tests ← xUnit WPF regression tests for `NumericInputControl`
└── NumericInputWpfTests.cs
Flowcharts
Detailed A3 flowcharts for NumericInputControl are available in:
docs/NumericInputControl-Flowcharts/README.mddocs/NumericInputControl-Flowcharts/Sheet-01-Macro-Overview.svgdocs/NumericInputControl-Flowcharts/Sheet-02-Bind-And-Refresh.svgdocs/NumericInputControl-Flowcharts/Sheet-03-Commit-Validation-Conversion.svgdocs/NumericInputControl-Flowcharts/Sheet-04-Interaction-And-Utilities.svg
Quick start
1. Decorate the model with [UserInterface]
using NumericInput.Core;
public class PressureVessel : INotifyPropertyChanged
{
[UserInterface(
Description = "Design Pressure",
UoM = "Pa", // native (invariant) unit
PhysicsFamily = PhysicsFamily.Pressure,
DecimalPlaces = 3,
MinValue = 0,
MaxValue = 25_000_000,
TooltipText = "ASME VIII max operating pressure")]
public double DesignPressure { get; set; } = 1_000_000; // 1 MPa
}
2. Add the namespace and control in XAML
<Window
xmlns:ni="clr-namespace:NumericInput.WPF;assembly=NumericInput.WPF"
xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib">
<ni:NumericInputControl
x:Name="ctrlPressure"
DescriptionWidth="160"
UoMWidth="60"
MinTextWidth="90"
MaxTextWidth="300"
DisplayUoMSymbol="bar"
AllowSpinners="True"
Increment="0.5"
SelectAllOnFocus="True"
MoveFocusOnEnter="True"/>
</Window>
3. Bind in code-behind
var vessel = new PressureVessel();
ctrlPressure.BindTo(vessel, nameof(PressureVessel.DesignPressure));
BindTo reads [UserInterface], sets Description / native unit / family / decimals,
subscribes to INotifyPropertyChanged, and shows the value converted to bar.
Dependency Properties
Layout
| Property | Type | Default | Description |
|---|---|---|---|
Description |
string |
"" |
Left label text |
DescriptionWidth |
double |
130 |
Fixed width of the description label (px) |
DescriptionTooltipText |
string |
"" |
Text shown in the popup when user clicks the description label |
IsDescriptionTooltipEnabled |
bool |
true |
Enables the description click-popup |
DisplayUoMSymbol |
string |
"" |
Unit symbol shown in the UoM label |
UoMWidth |
double |
60 |
Fixed width of the UoM label (px) |
ErrorIconWidth |
double |
24 |
Fixed width of the error icon (px) |
MinTextWidth |
double |
60 |
Minimum width of the text input column (px) |
MaxTextWidth |
double |
500 |
Maximum width of the text input column (px) |
IsTextWidthLocked |
bool |
false |
Locks the TextBox column to a fixed pixel width (useful for aligned stacked controls) |
Value and binding
| Property | Type | Default | Description |
|---|---|---|---|
ValueDouble |
double |
NaN |
Current value in display units (two-way bindable) |
PhysicsFamily |
PhysicsFamily |
Unknown |
Physical family — filters the unit drop-down |
HasError |
bool |
false |
True when the last commit produced a validation error |
ErrorText |
string |
"" |
Error message shown in the popup |
MinValue |
double |
NaN |
Minimum accepted value in native units (NaN = no limit) |
MaxValue |
double |
NaN |
Maximum accepted value in native units (NaN = no limit) |
CanValueBeNull |
bool |
false |
When true, an empty TextBox writes null to the bound property |
Formatting
| Property | Type | Default | Description |
|---|---|---|---|
DecimalPlaces |
int |
4 |
Decimal places shown (used when StringFormat and DecimalDigits are not set) |
GroupDigits |
bool |
true |
Include thousands separator in the formatted output |
UseInvariantCulture |
bool |
false |
Force dot as decimal separator regardless of the system culture |
Culture |
CultureInfo? |
null |
Explicit culture for parsing and formatting — highest priority (see Culture priority) |
StringFormat |
string? |
null |
Free-form format string passed to value.ToString(fmt, culture) — overrides DecimalPlaces / GroupDigits |
DecimalDigits |
int? |
null |
Decimal digits (positive = places shown; negative = rounding magnitude, e.g. -2 rounds to nearest 100) |
Formatting priority (highest → lowest)
StringFormat → DecimalDigits → DecimalPlaces + GroupDigits
StringFormat examples
| XAML | Input | Output (en-US) |
|---|---|---|
StringFormat="F2" |
1234.5 | 1234.50 |
StringFormat="N3" |
1234.5 | 1,234.500 |
StringFormat="E4" |
1234.5 | 1.2345E+003 |
StringFormat="P0" |
0.875 | 88% |
StringFormat="0.00" |
1234.5 | 1234.50 |
StringFormat="#,##0.##" |
1234.5 | 1,234.5 |
DecimalDigits examples
DecimalDigits |
Input | Rounded value | Display |
|---|---|---|---|
2 |
1234.5678 | — | 1,234.57 |
0 |
1234.5678 | — | 1,235 |
-1 |
1234.5678 | 1230 | 1,230 |
-2 |
1234.5678 | 1200 | 1,200 |
-3 |
1234.5678 | 1000 | 1,000 |
Input validation
| Property | Type | Default | Description |
|---|---|---|---|
NumberStyles |
NumberStyles |
Float, AllowThousands, AllowLeadingSign |
Which numeric patterns are accepted at parse time |
RegexPattern |
string |
"" |
Optional regex applied to raw text before parsing (100 ms timeout) |
ValidationTrigger |
ValidationTrigger |
LostFocus |
When validation runs (see below) |
NumberStyles examples
<ni:NumericInputControl NumberStyles="Integer"/>
<ni:NumericInputControl NumberStyles="AllowDecimalPoint, AllowThousands"/>
<ni:NumericInputControl NumberStyles="Float, AllowThousands, AllowLeadingSign"/>
ValidationTrigger
| Value | Behaviour |
|---|---|
LostFocus (default) |
Validate and commit on LostFocus or Enter. Invalid intermediate input is silently ignored while typing. |
PropertyChanged |
Validate on every keystroke. Valid input commits immediately; errors show without restoring the display so the user can keep editing. A final commit/restore still runs on LostFocus. |
UX helpers
| Property | Type | Default | Description |
|---|---|---|---|
SelectAllOnFocus |
bool |
true |
Select all text when the TextBox receives keyboard focus |
SelectAllOnClick |
bool |
false |
Select all text on mouse click |
SelectAllOnDoubleClick |
bool |
false |
Select all text on double-click |
MoveFocusOnEnter |
bool |
false |
Move focus to next control after a successful Enter commit |
Spinner
| Property | Type | Default | Description |
|---|---|---|---|
AllowSpinners |
bool |
false |
Show the ▲▼ spinner buttons |
Increment |
double |
1.0 |
Step size per click; clamped to MinValue/MaxValue |
SpinUpdateMode |
SpinUpdateMode |
PropertyChanged |
When the spinner writes back to the bound object |
SpinUpdateMode
| Value | Behaviour |
|---|---|
PropertyChanged (default) |
Each click immediately writes the new value to the bound object. |
AsBinding |
The TextBox is updated at each click, but the bound object is only written on LostFocus or Enter — same trigger as manual text editing. |
NumericBox — inheritable attached properties
The NumericBox static class exposes every key setting as an inheritable attached property.
Setting one on a parent container (Window, Grid, StackPanel…) propagates it to all
descendant NumericInputControl instances without having to repeat the attribute on each one.
A locally set instance property always wins over an inherited value.
xmlns:ni="clr-namespace:NumericInput.WPF;assembly=NumericInput.WPF"
xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
Culture for an entire form
<Grid ni:NumericBox.Culture="{x:Static glob:CultureInfo.InvariantCulture}">
<ni:NumericInputControl />
<ni:NumericInputControl />
</Grid>
Spinner buttons for all controls
<StackPanel ni:NumericBox.AllowSpinners="True"
ni:NumericBox.SpinUpdateMode="PropertyChanged">
<ni:NumericInputControl Increment="0.1" />
<ni:NumericInputControl Increment="10" />
<ni:NumericInputControl AllowSpinners="False" />
</StackPanel>
Uniform formatting across a panel
<Grid ni:NumericBox.StringFormat="F2">
<ni:NumericInputControl />
<ni:NumericInputControl StringFormat="N4" />
</Grid>
Keyboard UX on a Window level
<Window ni:NumericBox.SelectAllOnGotKeyboardFocus="True"
ni:NumericBox.MoveFocusOnEnter="True"
ni:NumericBox.ValidationTrigger="PropertyChanged">
</Window>
Complete attached property reference
| Attached Property | Type | Description |
|---|---|---|
NumericBox.Culture |
CultureInfo? |
Explicit culture for parsing and formatting |
NumericBox.NumberStyles |
NumberStyles? |
Allowed numeric patterns |
NumericBox.CanValueBeNull |
bool? |
Empty input → null |
NumericBox.StringFormat |
string? |
Free-form format string |
NumericBox.DecimalDigits |
int? |
Decimal places / rounding magnitude |
NumericBox.AllowSpinners |
bool? |
Show spinner buttons |
NumericBox.SpinUpdateMode |
SpinUpdateMode? |
When spinners write to the bound object |
NumericBox.MoveFocusOnEnter |
bool? |
Move focus on Enter |
NumericBox.SelectAllOnGotKeyboardFocus |
bool? |
Select all on keyboard focus |
NumericBox.SelectAllOnClick |
bool? |
Select all on mouse click |
NumericBox.SelectAllOnDoubleClick |
bool? |
Select all on double-click |
NumericBox.ValidationTrigger |
ValidationTrigger? |
When validation runs |
Commands
Two RoutedUICommand are exposed as static members of NumericInputControl:
| Command | Description |
|---|---|
NumericInputControl.IncrementCommand |
Increases the value by Increment (same as clicking ▲) |
NumericInputControl.DecrementCommand |
Decreases the value by Increment (same as clicking ▼) |
Both commands respect MinValue / MaxValue bounds and SpinUpdateMode.
CanExecute returns false when the control is disabled or read-only.
MVVM binding example
<Button Content="▲" Command="{x:Static ni:NumericInputControl.IncrementCommand}"
CommandTarget="{Binding ElementName=myControl}"/>
<Button Content="▼" Command="{x:Static ni:NumericInputControl.DecrementCommand}"
CommandTarget="{Binding ElementName=myControl}"/>
<ni:NumericInputControl x:Name="myControl" AllowSpinners="True" Increment="5"/>
Key binding example
<ni:NumericInputControl x:Name="myControl" AllowSpinners="True" Increment="1">
<ni:NumericInputControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static ni:NumericInputControl.IncrementCommand}"
CommandTarget="{Binding ElementName=myControl}"/>
<KeyBinding Key="Down" Command="{x:Static ni:NumericInputControl.DecrementCommand}"
CommandTarget="{Binding ElementName=myControl}"/>
</ni:NumericInputControl.InputBindings>
</ni:NumericInputControl>
Culture support and limitations
Culture priority (highest → lowest)
Culture (instance DP)
│ set explicitly on the control: Culture="it-IT"
▼
NumericBox.Culture (inherited attached)
│ set on a parent container, propagates to all children
▼
UseInvariantCulture = true
│ forces CultureInfo.InvariantCulture (dot decimal, no grouping)
▼
CultureInfo.CurrentCulture
system locale — the default when nothing else is set
Setting culture in XAML
<ni:NumericInputControl
Culture="{x:Static glob:CultureInfo.GetCultureInfo('it-IT')}"/>
<ni:NumericInputControl UseInvariantCulture="True"/>
<ni:NumericInputControl
Culture="{x:Static glob:CultureInfo.InvariantCulture}"/>
<Grid>
<Grid.Resources>
<glob:CultureInfo x:Key="Italian">it-IT</glob:CultureInfo>
</Grid.Resources>
</Grid>
Disambiguation rules for the number parser
The parser accepts both 1,234.5 (English) and 1.234,5 (Italian) in the same
control, choosing the right interpretation based on context:
| Separators found | Rule |
|---|---|
Both . and , |
The last separator is the decimal separator |
Single ,, not exactly 3 digits after it |
Comma is decimal → 1,5 → 1.5 |
Single ,, exactly 3 digits after it |
Decimal if culture uses , as decimal; otherwise thousands |
Single ., not exactly 3 digits after it |
Dot is decimal → 1.5 → 1.5 |
Single ., exactly 3 digits after it |
Thousands if culture uses , as decimal; otherwise decimal |
| Multiple same separators | All are thousands → 1.234.567 → 1234567 |
Supported culture groups
Group A — Comma decimal, dot thousands (fully supported ✅)
Cultures: it-IT, de-DE, es-ES, pt-PT, pt-BR, pl-PL, hr-HR, cs-CZ,
sk-SK, hu-HU, ro-RO, bg-BG, el-GR, sl-SI, uk-UA, tr-TR, ar-SA
Format: 1.234,56
Parse: "1.234,56" → 1234.56 ✅
Parse: "1234,56" → 1234.56 ✅
Parse: "1234.56" → 1234.56 ✅ (invariant format accepted)
Parse: "1,234.56" → 1234.56 ✅ (both separators → last is decimal)
Group B — Comma decimal, NBSP thousands ⚠️
Cultures: fr-FR, fr-BE, sv-SE, fi-FI, nb-NO, da-DK, nl-BE,
et-EE, lt-LT, lv-LV
Format (small): 123,45 → parses back correctly ✅
Format (large): 1 234,56 → NBSP separator — cannot be re-parsed ⚠️
Workaround: GroupDigits="False" eliminates the NBSP grouping
Limitation: The parser regex recognises only
.and,as separator characters. Non-breaking space (U+00A0), used by French and Nordic cultures as the thousands separator, is not matched. Small numbers (< 1000) and numbers formatted without digit grouping round-trip correctly. Large formatted numbers must be re-entered without the NBSP separator.Recommended fix: set
GroupDigits="False"orStringFormat="F2"for these cultures to suppress the thousands separator in the display output.
<Grid ni:NumericBox.Culture="{x:Static glob:CultureInfo.GetCultureInfo('fr-FR')}"
ni:NumericBox.StringFormat="F2">
<ni:NumericInputControl />
</Grid>
Group C — Dot decimal, comma thousands (fully supported ✅)
Cultures: en-US, en-GB, en-AU, nl-NL, ja-JP, zh-CN, ms-MY
Format: 1,234.56
Parse: "1,234.56" → 1234.56 ✅
Parse: "1234.56" → 1234.56 ✅
Parse: "1.234,56" → 1234.56 ✅ (both separators → last is decimal)
Group D — Dot decimal, apostrophe thousands ⚠️
Cultures: de-CH, fr-CH, it-CH
Format: 1'234.56 → RIGHT SINGLE QUOTATION MARK (U+2019)
Parse: "1'234.56" → FAILS — apostrophe not in separator regex ⚠️
Parse: "1234.56" → 1234.56 ✅
Parse: "1,234.56" → 1234.56 ✅ (comma treated as thousands)
Limitation: The Swiss apostrophe (U+2019) used by
de-CH/fr-CH/it-CHis not a recognised thousands separator. Numbers formatted by the system (e.g.1'234.56) cannot be re-parsed. Plain1234.56and comma-grouped1,234.56work correctly.Recommended fix:
GroupDigits="False"orStringFormat="F2".
Indian grouping ⚠️
Cultures: en-IN, hi-IN
Format: 1,00,000 → 2-2-3 digit grouping
Parse: "1,00,000" → read as 1.0 + spurious token — FAILS ⚠️
Parse: "100000" → 100000 ✅
Parse: "1,00,000 N" → FAILS ⚠️
Limitation: Indian numeric grouping uses 2-digit sub-groups after the first three digits (
1,00,000for one lakh). The parser expects groups of exactly 3 digits. Users should enter numbers without the thousands separator, or use standard 3-digit grouping (100,000).
Summary table
| Culture group | Examples | Fully supported | Limitation |
|---|---|---|---|
A — , decimal / . thousands |
it-IT de-DE es-ES pl-PL |
✅ Yes | — |
B — , decimal / NBSP thousands |
fr-FR sv-SE fi-FI nb-NO |
⚠️ Partial | Large formatted numbers not re-parseable |
C — . decimal / , thousands |
en-US en-GB ja-JP zh-CN |
✅ Yes | — |
D — . decimal / ' thousands |
de-CH fr-CH it-CH |
⚠️ Partial | Apostrophe not parseable as separator |
| Indian 2-2-3 grouping | en-IN hi-IN |
⚠️ Partial | 2-digit groups not recognised |
| InvariantCulture | — | ✅ Yes | — |
Expression evaluator
Prefix the input with = to evaluate an arithmetic or trigonometric expression.
The evaluator always uses InvariantCulture: dot (.) for decimal, comma (,)
to separate function arguments.
=1.5+2 → 3.5
=100/3 → 33.333…
=sqrt(2)*50 → 70.711…
=sin(pi/4) → 0.7071…
=round(3.14159,2) → 3.14
=clamp(150,0,100) → 100
Supported operators
| Token | Meaning |
|---|---|
+ - * / |
Arithmetic |
^ |
Power (right-associative) |
- + (unary) |
Sign |
( ) |
Grouping |
Constants
| Token | Value |
|---|---|
pi |
π ≈ 3.14159… |
e |
e ≈ 2.71828… |
phi |
φ ≈ 1.61803… |
Functions
| Category | Functions |
|---|---|
| Trigonometry (rad) | sin cos tan asin acos atan atan2 sinh cosh tanh |
| Trigonometry (deg) | sind cosd tand |
| Power / root | sqrt cbrt sq cube pow exp |
| Logarithm | ln log log10 log2 |
| Rounding | round floor ceil trunc |
| Misc | abs sign min max clamp hypot |
Physics families and units
Families
Adimensional · Ratio · Percentage · Angle ·
Length · Area · Volume · SecondMomentOfArea · SectionModulus ·
Force · Moment · Pressure · Stress · Energy · Power ·
Mass · LinearDensity · SurfaceDensity · VolumeDensity ·
Temperature · ThermalConductivity · HeatFlux ·
Time · Frequency · Velocity · Acceleration ·
ElectricVoltage · ElectricCurrent · ElectricResistance
Unit conversion model
base_value = value × Factor + Offset (to base unit)
value = (base_value − Offset) / Factor (from base unit)
Offset is non-zero only for temperature (°C, °F, K, °R).
Inline unit parsing
The TextBox accepts a number followed by an optional unit symbol. The unit is converted to the native unit and then to the display unit.
Control: Display = MPa, Native = Pa
"100 bar" → 100 bar = 10 000 000 Pa = 10.000 MPa ✅
"2500 psi" → 2500 psi ≈ 17.237 MPa ✅
"100 °C" → Error: incompatible family (Temperature) ❌
"=100/3" → 33.333… MPa (expression) ✅
Feature reference
| # | Feature | DP / API | Notes |
|---|---|---|---|
| A | Horizontal resize via Thumb | — | Right-edge drag handle |
| B | Vertical alignment | — | All sub-controls centred on the same baseline |
| C | Fixed-width columns | DescriptionWidth UoMWidth ErrorIconWidth |
|
| D | Locale-aware formatting | Culture UseInvariantCulture DecimalPlaces StringFormat DecimalDigits GroupDigits |
|
| E | Two-way binding | BindTo(obj, name) · ValueDouble |
INPC-aware |
| F | Automatic unit conversion | DisplayUoMSymbol ChangeDisplayUnit() |
|
| G | Unit catalogue | UnitRegistry · UnitConverter |
100+ units |
| H | Inline unit parsing | "100 bar" syntax |
|
| I | Unit drop-down | Click UoM label | Context menu per family |
| J | Immutable native unit | _nativeUnit |
Set once via [UserInterface] or BindTo |
| K | Ctrl+C / Ctrl+V | — | Copy = invariant number; Paste = commit |
| L | Error icon + popup | HasError ErrorText |
Click icon to toggle popup |
| M | [UserInterface] attribute |
Description UoM PhysicsFamily DecimalPlaces MinValue MaxValue TooltipText IsReadOnly |
Auto-populates on BindTo |
| N | Expression evaluator | =… prefix |
Arithmetic + trig, InvariantCulture |
| O | Description tooltip | DescriptionTooltipText IsDescriptionTooltipEnabled |
Click label to open popup |
| P | Locked TextBox width | IsTextWidthLocked |
Aligns stacked controls |
| Q | Bounds check | MinValue MaxValue |
Priority over [UserInterface] |
| R | Select all on focus | SelectAllOnFocus |
Default true |
| S | Select all on click/dbl-click | SelectAllOnClick SelectAllOnDoubleClick |
|
| T | Move focus on Enter | MoveFocusOnEnter |
Cycles to next focusable control |
| U | Spinner ▲▼ | AllowSpinners Increment SpinUpdateMode |
RepeatButton with Delay/Interval |
| V | Null value support | CanValueBeNull |
Empty → null on bound property |
| W | Culture override | Culture UseInvariantCulture |
Priority chain (see above) |
| X | NumberStyles | NumberStyles |
Restrict sign / decimal / exponent |
| Y | Regex validation | RegexPattern |
Applied before numeric parsing |
| Z | Validation trigger | ValidationTrigger |
LostFocus or PropertyChanged |
| Z1 | Commands | IncrementCommand DecrementCommand |
RoutedUICommand |
| Z2 | Inherited attached props | NumericBox.* |
Set once on container, propagates to children |
Build and test
# Restore and build the solution in Release mode
dotnet restore NumericInput.sln
dotnet build NumericInput.sln --configuration Release
# Run the full test suite (788 tests)
dotnet test NumericInput.Tests/NumericInput.Tests.csproj --configuration Release
# Launch the example application
dotnet run --project NumericInput.Example/NumericInput.Example.csproj --configuration Debug
# Run the WPF control regression tests
dotnet test NumericInput.WPF.Tests/NumericInput.WPF.Tests.csproj --configuration Debug
Debugging
- Open the solution in Visual Studio or Visual Studio Code.
- In VS Code, use the
Debugpanel and launch theExampleapp configuration, or rundotnet buildthen attach the debugger to the running process.
// .vscode/launch.json example
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Example App",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "🔨 Build — Debug (soluzione completa)",
"program": "${workspaceFolder}/NumericInput.Example/bin/Debug/net8.0-windows/NumericInput.Example.exe",
"cwd": "${workspaceFolder}/NumericInput.Example",
"console": "integratedTerminal",
"windows": {
"command": "${workspaceFolder}/NumericInput.Example/bin/Debug/net8.0-windows/NumericInput.Example.exe"
}
}
]
}
- In VS Code, the task name must match the
.vscode/tasks.jsonlabel:🔨 Build — Debug (soluzione completa). - To debug the control itself, set breakpoints in
NumericInput.WPF/NumericInputControl.xaml.cs,NumericInput.WPF/NumericBox.cs, orNumericInput.Core/UnitConverter.cs. - Use
dotnet test --filterto run a smaller subset of tests while iterating.
# Run a single test class
dotnet test NumericInput.Tests/NumericInput.Tests.csproj --filter "FullyQualifiedName~NumericInputTests"
Publish
- The example app formatting/culture/validation/UX demo controls use
PhysicsFamily="Adimensional"when no physical unit is configured, so raw unitless numeric input is parsed and displayed correctly.
# Publish the example WPF app for a chosen runtime
dotnet publish NumericInput.Example/NumericInput.Example.csproj \
--configuration Release \
--runtime win-x64 \
--self-contained true \
-p:PublishSingleFile=true \
-o publish/win-x64-sc
- Change
--runtimetowin-x86orwin-arm64as needed. - Set
--self-contained falsefor a framework-dependent build. - The output folder is
publish/<runtime>-<mode>when using the workspace tasks.
Pack for NuGet
# Pack the core library
dotnet pack NumericInput.Core/NumericInput.Core.csproj --configuration Release --output publish/nuget
# Pack the WPF library
dotnet pack NumericInput.WPF/NumericInput.WPF.csproj --configuration Release --output publish/nuget
NumericInput.Coreproduces the platform-agnostic package.NumericInput.WPFproduces the WPF control package.- Both packages include
README.mdand use the projectPackageIdvalues defined in the.csprojfiles.
Test coverage
| File | Tests | What is covered |
|---|---|---|
NumericInputTests.cs |
~300 | Core parsing, conversion, formatting, evaluator |
CultureParseTests.cs |
73 | it-IT, en-US, de-DE, InvariantCulture, round-trips, regressions |
CultureExtendedTests.cs |
413 | 32 cultures: all EU + ja-JP, zh-CN, ms-MY, en-IN, hi-IN |
Adding a new unit
Open NumericInput.Core/UnitRegistry.cs and add a line in BuildAll():
// Syntax:
// U("symbol", "Name", PhysicsFamily, factor, offset, "alias1", "alias2", …)
// where: base_value = value × factor + offset
// Example: kgf/cm² (1 kgf/cm² = 98 066.5 Pa)
U("kgf/cm²", "Kilogram-force per cm²", PhysicsFamily.Pressure, 98066.5, 0,
"at", "kgf/cm2"),
The unit is immediately available in the unit drop-down of every
NumericInputControl with PhysicsFamily = PhysicsFamily.Pressure.
Known limitations
| Limitation | Affected cultures | Workaround |
|---|---|---|
| NBSP thousands separator not parseable | fr-FR sv-SE fi-FI nb-NO da-DK nl-BE et-EE lt-LT lv-LV |
GroupDigits="False" or StringFormat="F2" |
| Apostrophe thousands separator not parseable | de-CH fr-CH it-CH |
GroupDigits="False" or type without separator |
| Indian 2-2-3 digit grouping not recognised | en-IN hi-IN |
Type without thousands separator or use 3-digit grouping |
| Touch keyboard | All | ShowTouchKeyboardOnTouchEnter not implemented |
| SpinnerDecorator | All | Spinners are built into the control — no separate decorator |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0-windows7.0 is compatible. net9.0-windows was computed. net10.0-windows was computed. |
-
net8.0-windows7.0
- NumericInput.Core (>= 1.2.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.