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

  1. Implement INumberParser (ParseDouble/ParseInt/ParseUInt) on your custom formatter in addition to INumberFormatter.
  2. Reuse the built-in ValidateNumberFormatter which already implements both.
  3. 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

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


AI-assisted analysis of lepoco/wpfui@ffebacd610 (2026-08-13). Data as JSON: /api/errors/30222bbe33fb6863. Report an issue: GitHub.