Skip to content

Commit fa2fa76

Browse files
committed
Improve decimal input handling and formatting robustness
- DecimalFormatter now preserves full fractional input (no truncation). - Added IsFormatting attached property to NumericEntryBehaviorBase to prevent recursive updates. - Enhanced OnEntryTextChanged to use delayed dispatcher for formatting, ensuring UI consistency. - Clarified CurrencyToStringConverter behavior and docs; now attempts currency parsing first in ConvertBack for better robustness with formatted input.
1 parent d452d6f commit fa2fa76

File tree

3 files changed

+45
-22
lines changed

3 files changed

+45
-22
lines changed

src/ISynergy.Framework.Core/Formatters/DecimalFormatter.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public override string FormatValue(decimal value)
2222
}
2323

2424
/// <summary>
25-
/// Cleans and normalizes decimal input, preserving the fractional part up to DecimalPlaces.
25+
/// Cleans and normalizes decimal input, preserving the fractional part.
2626
/// </summary>
2727
public override string CleanInput(string input)
2828
{
@@ -56,16 +56,10 @@ public override string CleanInput(string input)
5656
parts = new[] { parts[0], parts[1] };
5757
}
5858

59-
// Preserve integer part and fractional part (up to DecimalPlaces)
59+
// Preserve integer part and fractional part (do NOT truncate here)
6060
var integerPart = parts[0];
6161
var fractionalPart = parts.Length > 1 ? parts[1] : string.Empty;
6262

63-
// Limit fractional part to DecimalPlaces
64-
if (fractionalPart.Length > _decimalPlaces)
65-
{
66-
fractionalPart = fractionalPart.Substring(0, _decimalPlaces);
67-
}
68-
6963
// Rejoin with culture-specific decimal separator
7064
if (!string.IsNullOrEmpty(fractionalPart))
7165
{

src/ISynergy.Framework.UI.Maui/Behaviors/Base/NumericEntryBehaviorBase.cs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ public abstract class NumericEntryBehaviorBase : Behavior<Entry>
1212
private bool _isUpdating;
1313
private string _lastValidText = string.Empty;
1414

15+
/// <summary>
16+
/// Attached property to indicate the entry is being formatted by the behavior.
17+
/// </summary>
18+
public static readonly BindableProperty IsFormattingProperty = BindableProperty.CreateAttached(
19+
"IsFormatting",
20+
typeof(bool),
21+
typeof(NumericEntryBehaviorBase),
22+
defaultValue: false);
23+
24+
public static bool GetIsFormatting(BindableObject view) => (bool)view.GetValue(IsFormattingProperty);
25+
public static void SetIsFormatting(BindableObject view, bool value) => view.SetValue(IsFormattingProperty, value);
26+
1527
public static readonly BindableProperty MinimumValueProperty = BindableProperty.Create(
1628
nameof(MinimumValue),
1729
typeof(decimal?),
@@ -200,17 +212,27 @@ private void OnUnfocused(object? sender, FocusEventArgs e)
200212
if (TryParseInput(currentText, out var value) && IsValueValid(value))
201213
{
202214
_isUpdating = true;
203-
try
204-
{
205-
// Format will handle rounding
206-
var formatted = FormatValue(value);
207-
entry.Text = formatted;
208-
_lastValidText = formatted;
209-
}
210-
finally
215+
SetIsFormatting(entry, true);
216+
217+
// Format will handle rounding
218+
var formatted = FormatValue(value);
219+
_lastValidText = formatted;
220+
221+
// Set the formatted text immediately
222+
entry.Text = formatted;
223+
224+
// Use dispatcher to clear the formatting flag after the binding cycle completes
225+
entry.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(50), () =>
211226
{
227+
SetIsFormatting(entry, false);
212228
_isUpdating = false;
213-
}
229+
230+
// Reapply formatted text if it was overwritten
231+
if (entry.Text != formatted && !entry.IsFocused)
232+
{
233+
entry.Text = formatted;
234+
}
235+
});
214236
}
215237
else
216238
{

src/ISynergy.Framework.UI.Maui/Converters/CurrencyConverters.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,22 @@ public object ConvertBack(object? value, Type targetType, object? parameter, Cul
4949
public class CurrencyToStringConverter : IValueConverter
5050
{
5151
/// <summary>
52-
/// Converts decimal to string without formatting (raw value).
52+
/// Converts decimal to string for display.
5353
/// </summary>
5454
/// <param name="value">The decimal value.</param>
5555
/// <param name="targetType">Type of the target.</param>
56-
/// <param name="parameter">Optional: Not used - behavior handles formatting.</param>
56+
/// <param name="parameter">Optional: Pass "formatted" to return currency-formatted value.</param>
5757
/// <param name="culture">The culture.</param>
58-
/// <returns>Raw string representation or empty string for zero values.</returns>
58+
/// <returns>String representation for entry binding.</returns>
5959
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
6060
{
6161
if (value is decimal decimalValue)
6262
{
6363
if (decimalValue == 0m)
6464
return string.Empty;
6565

66-
// Return raw value without formatting - behavior will format on blur
66+
// Return raw value for editing - the behavior will format with currency symbol on blur
67+
// The behavior handles the formatting lifecycle (raw during edit, formatted on blur)
6768
return decimalValue.ToString(culture);
6869
}
6970
return string.Empty;
@@ -81,7 +82,13 @@ public object ConvertBack(object? value, Type targetType, object? parameter, Cul
8182
{
8283
if (value is string stringValue && !string.IsNullOrWhiteSpace(stringValue))
8384
{
84-
// Remove currency symbols and other formatting - accept any valid input
85+
// Try to parse as currency first (handles formatted values with currency symbols)
86+
if (decimal.TryParse(stringValue, NumberStyles.Currency, culture, out var currencyResult))
87+
{
88+
return currencyResult;
89+
}
90+
91+
// Fallback: Remove currency symbols and other formatting - accept any valid input
8592
var cleaned = new string(stringValue.Where(c =>
8693
char.IsDigit(c) ||
8794
c.ToString() == culture.NumberFormat.NumberDecimalSeparator ||

0 commit comments

Comments
 (0)