diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj
index 3cd6540..4bbb6e9 100644
--- a/TransportPlugin.csproj
+++ b/TransportPlugin.csproj
@@ -307,6 +307,7 @@
+
diff --git a/src/UI/WPF/Converters/EditableNumberConverter.cs b/src/UI/WPF/Converters/EditableNumberConverter.cs
new file mode 100644
index 0000000..ee7886a
--- /dev/null
+++ b/src/UI/WPF/Converters/EditableNumberConverter.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace NavisworksTransport.UI.WPF.Converters
+{
+ ///
+ /// Keeps numeric TextBox editing humane: incomplete text is allowed while editing,
+ /// and only complete numeric values are written back to the bound source.
+ ///
+ 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))
+ {
+ throw new FormatException("请输入有效的数字。");
+ }
+
+ text = text.Trim();
+ Type actualTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
+
+ if (TryParseNumber(text, culture, actualTargetType, out object parsedValue))
+ {
+ return parsedValue;
+ }
+
+ throw new FormatException("请输入有效的数字。");
+ }
+
+ 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);
+ }
+ }
+}
diff --git a/src/UI/WPF/Views/AnimationControlView.xaml b/src/UI/WPF/Views/AnimationControlView.xaml
index f6226aa..eb9efcb 100644
--- a/src/UI/WPF/Views/AnimationControlView.xaml
+++ b/src/UI/WPF/Views/AnimationControlView.xaml
@@ -27,6 +27,7 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
+