值转换器用于评估用户输入的方程式





5.00/5 (10投票s)
此值转换器可以非常快速轻松地为用户在绑定到数字属性的文本框中输入方程式的功能添加能力。
引言
我知道有很多时候,让用户能够在一个TextBox
中输入方程式,而不是让用户在输入前计算出值,这会非常方便。我曾参与过实现此功能的项目。
历史
我正在开发一个值转换器,它允许绑定到计算值。这促使我搜索互联网,看看有哪些可用的字符串评估方法。一些想法引起了我的注意,我决定使用 JavaScript 的Eval()
函数。在我开发过程中,我突然意识到我可以反过来做,得到一个值转换器,它可以评估字符串并返回计算值给绑定元素。它实际上比我想象的要好。
实现
要使用JavaScript,必须创建一个JavaScript函数
package JavascriptEvaluator { class JavascriptEvaluator { public function Evaluate(expr : String) : String { return eval(expr, "unsafe"); } } }
我将此代码放入了一个名为JavascriptEvaluator.js的文件中。
接下来,必须将其编译成一个程序集。通过在 Visual Studio 命令提示符中执行以下命令,将在JavascriptEvaluator.dll文件中创建此程序集
jsc /target:library JavascriptEvaluator.js
我将此命令放入了一个名为JavascriptEvaluatorCompile.bat的批处理文件中。
在使用此功能的项目中,必须将对JavascriptEvaluator.js和Microsoft.Jscript的引用添加进去。您必须浏览到JavascriptEvaluator.js文件的位置以添加它,而Microsoft.Jscript程序集包含在 Framework Assemblies 中。
值转换器实际上非常简单,因为只有ConvertBack
方法包含任何重要代码,而且代码量也很少。
class EvaluationValueConverter : IValueConverter { private readonly JavascriptEvaluator.JavascriptEvaluator evaluator = new JavascriptEvaluator.JavascriptEvaluator(); public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value == null) return null; try { return evaluator.Evaluate(value.ToString()); } catch (Exception e) { return null; } }
由于ViewModel
中的值没有进行任何处理,Convert
方法仅返回值参数。ConvertBack
只需要将转换为字符串的值参数通过JavaScriptEvaluator
类实例的Evaluate
方法传递给JavaScript的Eval()
方法。如果由于字符串无法评估而抛出异常,则会抛出异常,然后将 null 传递给ViewModel
。好处是 WPF 的TextBox
会自动处理传递给ViewModel
的 null 值的问题;当属性类型为decimal
或某些其他非Nullable
类型时,它会将TextBox
的边框更改为红色。好处是TextBox
中的方程式仍然可以编辑以纠正错误。
显然,这并不理想,因为ViewModel
中的属性值与显示的值不对应,并且ViewModel
也不知道用户输入有问题。
计算引擎选项
JavaScript解决方案虽然对我的原始要求来说相当不错,但实际上并没有那么好。最重要的问题是对超越函数(transcendental functions)的支持:JavaScript支持最重要的超越函数,并且Eval()
函数会处理它们,但它们区分大小写,并且必须在前面加上“Math.
”。另一个缺点是它不支持指数运算符(“^”)——而是必须使用pow
函数。理想情况下,可以创建一个更好的评估器,并且可以在互联网上找到许多不错的评估器,但我不想过于冒犯他人。Eval()
函数非常强大,并且可以做比任何人可能想要的更多的功能。做一些工作就可以解决pow
函数和区分大小写的问题。
我在互联网上找到了许多其他选项
- Calculator.NET - 一个评估数学表达式的计算器(http://weblogs.asp.net/pwelter34/archive/2007/05/05/calculator-net-calculator-that-evaluates-math-expressions.aspx)。我喜欢这个,因为它也能计算字符串,但这对于本应用来说并不重要,而且它还可以进行转换。它还有一个前端,很好地替代了标准计算器。
- 通过在运行时编译 C# 代码来评估数学表达式(https://codeproject.org.cn/KB/recipes/matheval.aspx)。此选项将方程式编译成 C#。这可能不是最佳选择,因为它存在JavaScript方法的大部分问题,并且编译后的代码可能存在垃圾回收问题。
- 使用正则表达式的 C# 公式评估器(http://www.jarloo.com/c-formula-evaluator)。我非常喜欢这个选项,特别是因为它易于扩展。它将是速度最慢的之一,需要评估大量正则表达式,但对于用户输入来说,可能不是什么大问题。
- “表达式评估器重访”(100% 托管 .NET 中的
Eval
函数)(https://codeproject.org.cn/KB/recipes/eval3.aspx)。另一个用 C# 编写的实现。 - Flee(快速轻量级表达式评估器)(https://codeproject.org.cn/KB/recipes/Flee.aspx)
它们都是不错的选择,其中一些会比我的方案更好,因为它们对用户来说更直观。
示例用法
这里用户输入了一个方程式,但还没有离开TextBox
,因此值转换器还没有处理该方程式以确定它是否无效。
离开TextBox
后,值转换器已确定用户输入有错误,并向属性返回了 null,导致View
识别出错误。
这里用户正在输入一个包含超越函数的方程式(这是在焦点离开TextBox
之前)。
焦点离开TextBox
后,显示以下内容:
修复用户输入错误检测
这种方法的主要问题是,如果绑定到数字属性,ViewModel
无法得知方程式错误。View
也不知道错误,因此无法禁用继续操作。这可以通过使数字成为Nullable
来修复,但这样用户看到的值将被重置,从而用户无法修复方程式,并且红色错误边框也不会显示。如果知道输入错误很重要,那么ViewModel
将需要进行增强,并且需要使用多值转换器,或者将评估移到属性中(在这种情况下,将在属性设置中使用JavaScript的Eval()
方法尝试评估用户输入,该属性将是字符串类型)。
最后一个解决方案我认为是最好的。如果需要在表单上指示错误,则可以使用值转换器来设置某个属性(边框、背景等),如果该属性为非数字,则将其设置为无效值。ViewModel
将知道该属性无效,因为它不是数字。唯一剩下的就是将属性在字符串值和模型所需的正确数字值之间进行转换。这需要做更多的工作,但对于增加的功能来说是可以接受的。
我想说的是,这是 Microsoft 在WPF中遗漏的一个明显的功能;显然存在错误,但无法将此信息传递给ViewModel
。
警告
目前,值转换器不是非常智能,如果此转换器绑定到整数类型(例如Integer
)格式,并且Eval()
方法返回浮点值或定点值,则会引发错误。可以通过添加代码来轻松修复此问题,该代码可以确定要绑定的属性的类型,并在返回值之前进行转换。这也将是溢出问题。
结论
此值转换器可以非常快速轻松地为用户在绑定到数字属性的文本框中输入方程式的功能添加能力。它存在一个问题,即如果用户输入了无效的方程式,ViewModel
将不会知道问题存在,因此可能继续使用文本框中的无效方程式。如果这种情况可以接受,那么这是一个很好的选择。
此外,本项目展示了如何使用JavaScript的Eval()
方法来评估字符串,并且可以在其他更适合应用程序的方式中使用。