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.
128 lines
4.2 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|