NavisworksTransport/src/UI/WPF/Converters/EditableNumberConverter.cs
tian 6bc77672e9 fix: prevent crash when invalid characters typed in coordinate editor
EditableNumberConverter.ConvertBack was throwing FormatException
when users typed '=' or other non-numeric characters, causing
WPF binding validation to crash the application on focus change.
Changed to return Binding.DoNothing to silently reject invalid input.
2026-06-03 19:49:44 +08:00

128 lines
4.2 KiB
C#

using System;
using System.Globalization;
using System.Windows.Data;
namespace NavisworksTransport.UI.WPF.Converters
{
/// <summary>
/// Keeps numeric TextBox editing humane: incomplete text is allowed while editing,
/// and only complete numeric values are written back to the bound source.
/// </summary>
public class EditableNumberConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return string.Empty;
}
string format = parameter as string;
if (string.IsNullOrWhiteSpace(format))
{
return System.Convert.ToString(value, culture);
}
if (value is IFormattable formattable)
{
return formattable.ToString(format, culture);
}
return System.Convert.ToString(value, culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = value as string;
if (IsIntermediateText(text))
{
return Binding.DoNothing;
}
text = text.Trim();
Type actualTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
if (TryParseNumber(text, culture, actualTargetType, out object parsedValue))
{
return parsedValue;
}
return Binding.DoNothing;
}
public static bool IsIntermediateText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return true;
}
text = text.Trim();
return text == "-" ||
text == "+" ||
text == "." ||
text == "-." ||
text == "+.";
}
public static bool TryParseNumber(string text, CultureInfo culture, Type targetType, out object parsedValue)
{
NumberStyles styles = NumberStyles.Float | NumberStyles.AllowThousands;
if (targetType == typeof(double))
{
if (TryParseDouble(text, styles, culture, out double doubleValue))
{
parsedValue = doubleValue;
return true;
}
}
else if (targetType == typeof(float))
{
if (TryParseDouble(text, styles, culture, out double doubleValue))
{
parsedValue = (float)doubleValue;
return true;
}
}
else if (targetType == typeof(decimal))
{
if (decimal.TryParse(text, styles, culture, out decimal decimalValue) ||
decimal.TryParse(text, styles, CultureInfo.InvariantCulture, out decimalValue))
{
parsedValue = decimalValue;
return true;
}
}
else if (targetType == typeof(int))
{
if (int.TryParse(text, NumberStyles.Integer, culture, out int intValue) ||
int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out intValue))
{
parsedValue = intValue;
return true;
}
}
else if (targetType == typeof(long))
{
if (long.TryParse(text, NumberStyles.Integer, culture, out long longValue) ||
long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
{
parsedValue = longValue;
return true;
}
}
parsedValue = null;
return false;
}
private static bool TryParseDouble(string text, NumberStyles styles, CultureInfo culture, out double value)
{
return double.TryParse(text, styles, culture, out value) ||
double.TryParse(text, styles, CultureInfo.InvariantCulture, out value);
}
}
}