lepoco/wpfui · error · InvalidOperationException
{nameof(NumberFormatter)} must implement {typeof(INumberPars
Error message
{nameof(NumberFormatter)} must implement {typeof(INumberParser)} What it means
NumberBox exposes NumberFormatter as INumberFormatter, but the property-changed callback OnNumberFormatterChanged additionally requires the assigned object to implement INumberParser (so the control can parse user-typed text back into numbers). Assigning any INumberFormatter that lacks INumberParser throws InvalidOperationException at the moment SetValue runs. The built-in ValidateNumberFormatter implements both interfaces; custom formatters must too.
Source
Thrown at src/Wpf.Ui/Controls/NumberBox/NumberBox.cs:521
private static INumberFormatter GetRegionalSettingsAwareDecimalFormatter()
{
return new ValidateNumberFormatter();
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is NumberBox numberBox)
{
numberBox.OnValueChanged(d, (double?)e.OldValue);
}
}
private static void OnNumberFormatterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is not INumberParser)
{
throw new InvalidOperationException(
$"{nameof(NumberFormatter)} must implement {typeof(INumberParser)}"
);
}
}
private static void OnMaxDecimalPlacesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not NumberBox numberBox)
{
return;
}
if (numberBox.Value is double currentValue)
{
numberBox.SetCurrentValue(ValueProperty, Math.Round(currentValue, numberBox.MaxDecimalPlaces));
}
}
View on GitHub (pinned to ffebacd610)
Solutions
- Implement INumberParser (ParseDouble/ParseInt/ParseUInt) on your custom formatter in addition to INumberFormatter.
- Reuse the built-in ValidateNumberFormatter which already implements both.
- Decorate/wrap the format-only formatter inside a class that delegates parsing to a parser you control.
Example fix
// before
public class MoneyFormatter : INumberFormatter
{
public string FormatDouble(double value) => value.ToString("C");
}
box.NumberFormatter = new MoneyFormatter(); // throws
// after
public class MoneyFormatter : INumberFormatter, INumberParser
{
public string FormatDouble(double value) => value.ToString("C");
public double? ParseDouble(string text) => double.TryParse(text, NumberStyles.Currency, CultureInfo.CurrentCulture, out var v) ? v : null;
public int? ParseInt(string text) => (int?)ParseDouble(text);
public uint? ParseUInt(string text) => (uint?)ParseDouble(text);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (formatter is not INumberParser)
{
throw new ArgumentException("NumberFormatter must also implement INumberParser.", nameof(formatter));
}
box.NumberFormatter = formatter; Type guard
static bool IsCompatibleFormatter(object? f) => f is INumberFormatter and INumberParser;
Try / catch
try { box.NumberFormatter = customFormatter; }
catch (InvalidOperationException ex) when (ex.Message.Contains("INumberParser"))
{
_logger.LogError(ex, "Formatter does not implement INumberParser");
} Prevention
- Implement both INumberFormatter and INumberParser on custom formatters.
- Prefer the built-in ValidateNumberFormatter unless you need custom formatting.
- Add a unit test asserting your formatter implements INumberParser.
When it happens
Trigger: Calling numberBox.NumberFormatter = myFormatter where myFormatter implements INumberFormatter but not INumberParser; passing a WinUI/Windows.Globalization.NumberFormatting INumberFormatter that only formats.
Common situations: Substituting a third-party formatter that formats-only; deriving from a formatter base that does not implement parsing; partial port of a formatter where the ParseNum/ParseDouble methods were left out.
Related errors
- Unable to find the base directory of the application.
- Unable to determine the window source.
- Cannot change DialogHostEx while the dialog is being shown.
- DialogHost was not set
- Only one ContentDialogHost instance is allowed per Window.
AI-assisted analysis of lepoco/wpfui@ffebacd610 (2026-08-13).
Data as JSON: /api/errors/30222bbe33fb6863.
Report an issue: GitHub.