NumericInput.WPF 1.2.1

There is a newer version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NumericInput.WPF" Version="1.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NumericInput.WPF" Version="1.2.1" />
                    
Directory.Packages.props
<PackageReference Include="NumericInput.WPF" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NumericInput.WPF --version 1.2.1
                    
#r "nuget: NumericInput.WPF, 1.2.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NumericInput.WPF@1.2.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NumericInput.WPF&version=1.2.1
                    
Install as a Cake Addin
#tool nuget:?package=NumericInput.WPF&version=1.2.1
                    
Install as a Cake Tool

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

  1. Packages: Core vs WPF
  2. Installation
  3. Solution structure
  4. Quick start
  5. Dependency Properties
  6. NumericBox — inheritable attached properties
  7. Commands
  8. Culture support and limitations
  9. Expression evaluator
  10. Physics families and units
  11. Feature reference
  12. Build and test
  13. Adding a new unit
  14. 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.WPFNumericInput.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.md
  • docs/NumericInputControl-Flowcharts/Sheet-01-Macro-Overview.svg
  • docs/NumericInputControl-Flowcharts/Sheet-02-Bind-And-Refresh.svg
  • docs/NumericInputControl-Flowcharts/Sheet-03-Commit-Validation-Conversion.svg
  • docs/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,51.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.51.5
Single ., exactly 3 digits after it Thousands if culture uses , as decimal; otherwise decimal
Multiple same separators All are thousands → 1.234.5671234567

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" or StringFormat="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-CH is not a recognised thousands separator. Numbers formatted by the system (e.g. 1'234.56) cannot be re-parsed. Plain 1234.56 and comma-grouped 1,234.56 work correctly.

Recommended fix: GroupDigits="False" or StringFormat="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,000 for 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 Debug panel and launch the Example app configuration, or run dotnet build then 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.json label: 🔨 Build — Debug (soluzione completa).
  • To debug the control itself, set breakpoints in NumericInput.WPF/NumericInputControl.xaml.cs, NumericInput.WPF/NumericBox.cs, or NumericInput.Core/UnitConverter.cs.
  • Use dotnet test --filter to 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 --runtime to win-x86 or win-arm64 as needed.
  • Set --self-contained false for 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.Core produces the platform-agnostic package.
  • NumericInput.WPF produces the WPF control package.
  • Both packages include README.md and use the project PackageId values defined in the .csproj files.

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 Compatible and additional computed target framework versions.
.NET net8.0-windows7.0 is compatible.  net9.0-windows was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 0 8/24/2026
1.2.1 108 7/25/2026