You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MudBlazor:如何在自定义十进制输入组件中启用实时千分位分隔符?

实现Blazor MudTextField实时千分位格式化+聚焦全选

需求:

  • 接收十进制数字,输入时立即显示千分位分隔符(例:输入123456.78实时显示123,456.78)
  • 字段获得焦点时自动全选所有文本

当前代码仅在失去焦点后才显示格式化文本,无法实时生效,原代码如下:

@using System.Globalization

<MudTextField T="string"
              @ref="_input"
              Text="@_displayValue"
              TextChanged="@OnTextChanged"
              Immediate="true"
              Placeholder="@Placeholder"
              Margin="Margin.Dense"
              Variant="@Variant"
              Class="@($"small-input {Class}")"
              Pattern="^[0-9]*\.?[0-9]+$"
              @onfocus="OnInputFocus"/>

@code
{
    [Parameter] public decimal? Value { get; set; }
    [Parameter] public EventCallback<decimal> ValueChanged { get; set; }
    [Parameter] public string? Placeholder { get; set; }
    [Parameter] public Variant Variant { get; set; } = Variant.Outlined;
    [Parameter] public string Class { get; set; } = string.Empty;

    private MudTextField<string> _input = default!;
    private string _displayValue = string.Empty;

    protected override void OnInitialized()
    {
        if (Value.HasValue)
        {
            _displayValue = FormatNumber(Value.Value);
        }
    }

    private void OnTextChanged(string newValue)
    {
        var raw = newValue.Replace(",", "");
        if (decimal.TryParse(raw, out var parsed))
        {
            Value = parsed;
            ValueChanged.InvokeAsync(parsed);
            _displayValue = FormatNumber(parsed);
        }
        else
        {
            _displayValue = newValue;
        }
    }

    private async Task OnInputFocus(FocusEventArgs args)
    {
        await _input.SelectAsync();
    }

    private string FormatNumber(decimal number) => number.ToString("###,##0.##", CultureInfo.InvariantCulture);
}

解决方案

修改后的代码

@using System.Globalization
@using System.Text.RegularExpressions

<MudTextField T="string"
              @ref="_input"
              @bind-Text="_displayValue"
              Immediate="true"
              Placeholder="@Placeholder"
              Margin="Margin.Dense"
              Variant="@Variant"
              Class="@($"small-input {Class}")"
              @oninput="OnInput"
              @onfocus="OnInputFocus"/>

@code {
    [Parameter] public decimal? Value { get; set; }
    [Parameter] public EventCallback<decimal> ValueChanged { get; set; }
    [Parameter] public string? Placeholder { get; set; }
    [Parameter] public Variant Variant { get; set; } = Variant.Outlined;
    [Parameter] public string Class { get; set; } = string.Empty;

    private MudTextField<string> _input = default!;
    private string _displayValue = string.Empty;
    private int _cursorPosition;

    protected override void OnInitialized()
    {
        if (Value.HasValue)
        {
            _displayValue = FormatNumber(Value.Value);
        }
    }

    private async Task OnInput(ChangeEventArgs e)
    {
        var inputValue = e.Value?.ToString() ?? string.Empty;
        // 过滤非法字符,仅保留数字和单个小数点
        var rawValue = Regex.Replace(inputValue, @"[^0-9.]", "");
        var parts = rawValue.Split('.');
        if (parts.Length > 2)
        {
            rawValue = $"{parts[0]}.{string.Join("", parts.Skip(1))}";
        }

        if (decimal.TryParse(rawValue, out var parsedDecimal))
        {
            Value = parsedDecimal;
            await ValueChanged.InvokeAsync(parsedDecimal);
            
            // 保存当前光标位置
            _cursorPosition = await _input.GetCursorPositionAsync();
            var formattedValue = FormatNumber(parsedDecimal);
            // 计算新增的千分位逗号数量
            var commasAdded = formattedValue.Count(c => c == ',');
            
            // 根据原光标位置调整(小数点前的位置需要加上逗号数量,小数点后的位置不变)
            if (!rawValue.Contains('.'))
            {
                _cursorPosition += commasAdded;
            }
            else
            {
                var dotIndexInRaw = rawValue.IndexOf('.');
                if (_cursorPosition <= dotIndexInRaw)
                {
                    _cursorPosition += commasAdded;
                }
            }
            
            _displayValue = formattedValue;
            // 延迟确保DOM更新后恢复光标位置
            await Task.Delay(1);
            await _input.SetCursorPositionAsync(_cursorPosition);
        }
        else
        {
            _displayValue = inputValue;
        }
    }

    private async Task OnInputFocus(FocusEventArgs args)
    {
        await Task.Delay(1);
        await _input.SelectAsync();
    }

    private string FormatNumber(decimal number) => number.ToString("###,##0.##", CultureInfo.InvariantCulture);
}

核心修改说明

  1. 替换触发事件:用@oninput替代TextChanged,确保每一次按键输入都实时触发处理逻辑,而非等待输入框状态变更。
  2. 光标位置处理:格式化时保存并恢复光标位置,避免添加千分位逗号后光标跳转到末尾,提升输入流畅度。
  3. 输入合法性校验:用正则过滤非数字/小数点字符,同时确保仅保留一个小数点,避免无效输入。
  4. 聚焦全选优化:增加Task.Delay(1)确保组件渲染完成后再执行全选,避免因DOM未更新导致的全选失效。

内容的提问来源于stack exchange,提问作者Alireza Noori

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:13:14