.NET MAUI升级到9.0后货币输入控件报end should be < than charSequence length错误
.NET MAUI 9.0自定义货币Entry控件报错:Java.Lang.IllegalArgumentException: 'end should be < than charSequence length'
我在.NET MAUI应用中实现了一个响应式货币输入Entry控件,初始显示R$ 0,00:输入5后变为R$ 0,05,输入3后变为R$ 0,53,功能原本完全正常。但将.NET版本升级到9.0后,当输入到R$ 159,00再按0时,触发如下错误:
Java.Lang.IllegalArgumentException: 'end should be < than charSequence length'
怀疑是格式化后金额中的千分位“.”导致了该问题,相关代码如下:
using System.Globalization; namespace Mobile; public class EntryValores : Entry { private bool executingTextChanged = false; public EntryValores() { // HorizontalTextAlignment = TextAlignment.End; TextChanged += EntryValores_OnTextChanged; } protected void EntryValores_OnTextChanged(object sender, TextChangedEventArgs e) { if (executingTextChanged) return; executingTextChanged = true; // Formatar valores decimal valor; string valorTexto = "0"; // Retornar apenas os dígitos foreach (char c in Text) { if (char.IsDigit(c)) { valorTexto += c; } } // Se for Nulo, então colocar 0 if (string.IsNullOrEmpty(valorTexto)) { valorTexto = "0"; } valor = decimal.Parse(valorTexto) / 100; // Verificar limites if (valor > 999999999999m || valor < -999999999999m) { // Se o valor for maior que 100 trilhões ou menor que -100 trilhões, cancelar a entrada Text = e.OldTextValue; } else { // Colocar o valor formatado Text = valor.ToString("C", CultureInfo.CurrentCulture); } executingTextChanged = false; } }
问题原因
这个错误本质是Android端Entry控件的光标位置计算异常。.NET MAUI 9.0对Android平台的文本输入控件逻辑做了调整:当你在TextChanged事件中直接替换文本时,若格式化后的文本因千分位插入了额外字符(比如输入0后R$ 159,00变为R$ 1.590,00,多了一个.),控件内部的光标位置没有同步更新,导致尝试访问超出字符长度的索引,触发参数错误。
修复方案
方案1:手动同步光标位置(快速修复)
在更新Text后,强制将光标移到文本末尾,避免控件内部光标位置错乱:
else { // Colocar o valor formatado Text = valor.ToString("C", CultureInfo.CurrentCulture); // 新增:将光标定位到文本末尾 Dispatcher.Dispatch(() => { if (!string.IsNullOrEmpty(Text)) { CursorPosition = Text.Length; SelectionLength = 0; } }); }
使用Dispatcher.Dispatch是为了确保光标操作在UI线程执行,避免跨线程异常。
方案2:通过绑定属性处理(更稳妥)
改用绑定属性+转换器的方式处理格式化,避开直接修改Text带来的光标同步问题:
- 新增绑定属性:
public static readonly BindableProperty ValueProperty = BindableProperty.Create(nameof(Value), typeof(decimal), typeof(EntryValores), 0m, propertyChanged: (bindable, oldValue, newValue) => { var entry = (EntryValores)bindable; if (!entry.executingTextChanged) { entry.executingTextChanged = true; entry.Text = ((decimal)newValue).ToString("C", CultureInfo.CurrentCulture); entry.executingTextChanged = false; } }); public decimal Value { get => (decimal)GetValue(ValueProperty); set => SetValue(ValueProperty, value); }
- 修改
TextChanged逻辑,更新绑定属性而非直接修改Text:
protected void EntryValores_OnTextChanged(object sender, TextChangedEventArgs e) { if (executingTextChanged) return; executingTextChanged = true; // 简化数字提取逻辑 string valorTexto = string.Concat(Text.Where(char.IsDigit)); valorTexto = string.IsNullOrEmpty(valorTexto) ? "0" : valorTexto; if (decimal.TryParse(valorTexto, out decimal rawValue)) { decimal valor = rawValue / 100; if (valor > 999999999999m || valor < -999999999999m) { // 超出范围则保持原值 Value = (decimal)GetValue(ValueProperty); } else { Value = valor; } } else { Value = 0m; } executingTextChanged = false; }
额外优化
- 用
string.Concat(Text.Where(char.IsDigit))替代遍历字符的逻辑,更简洁高效。 - 替换
decimal.Parse为decimal.TryParse,避免非法输入导致的解析异常。
内容的提问来源于stack exchange,提问作者Heitor Badotti
相关产品推荐
相关产品推荐

