在开发应用程序时,经常需要处理用户输入的数值。通常情况下,用户需要先计算出结果,然后再输入。但是,如果能够允许用户直接在文本框中输入方程式,然后自动计算结果并绑定到数值属性,那将大大提高用户体验。本文将介绍如何实现这一功能。
在之前的项目中,实现了一个值转换器,它允许绑定到一个计算后的值。这让开始在互联网上寻找能够评估字符串的方法。注意到了一些有趣的想法,并决定使用JavaScript的Eval()函数。在实现过程中,突然意识到,可以反过来做,得到一个值转换器,它可以评估一个字符串并返回计算后的值给绑定元素。结果比预想的要好。
要使用JavaScript,首先需要创建一个JavaScript函数:
package JavascriptEvaluator {
class JavascriptEvaluator {
public function Evaluate(expr: String): String {
return eval(expr, "unsafe");
}
}
}
将这段代码放入了一个名为JavascriptEvaluator.js的文件中。接下来,需要将这个文件编译成一个程序集。这个程序集将在执行以下命令后创建在JavascriptEvaluator.dll文件中:
jsc /target:library JavascriptEvaluator.js
将这个命令放入了一个名为JavascriptEvaluatorCompile.bat的批处理文件中。
在使用这个函数的项目中,需要添加对JavascriptEvaluator.js和Microsoft.Jscript的引用。需要浏览到JavascriptEvaluator.js文件的位置来添加它,而Microsoft.Jscript程序集包含在框架程序集中。
值转换器非常简单,因为只有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()方法。如果因为字符串无法评估而抛出异常,那么就会抛出异常,然后向ViewModel传递null。一个很好的特点是WPF TextBox会自动处理传递给ViewModel的null值的问题;如果属性是decimal或其他非Nullable类型,它会将TextBox边框变为红色。这样,用户仍然可以编辑TextBox中的方程式以纠正错误。
虽然JavaScript解决方案对于最初的需求来说相当不错,但并不是很好。最显著的问题是不支持超越函数:JavaScript支持最重要的超越函数,Eval()函数也会处理这些函数,但它们是大小写敏感的,并且必须以“Math.”开头。另一个缺点是它不支持指数运算符(“^”)——相反,必须使用pow函数。理想情况下,可以创建一个更好的评估器,互联网上可以找到许多好的评估器,但不想冒犯别人。Eval()函数非常强大,可以做比任何人都想要的更多的事情。稍微修改一下就可以解决pow函数和大小写敏感的问题。
在互联网上找到了一些其他的选项:
这些都是很好的选择,有几个会比更好,因为它们对用户来说更直观。
这里用户输入了一个方程式,但还没有离开TextBox,所以值转换器还没有处理方程式以确定它是否无效:
在离开TextBox后,值转换器确定用户输入有错误,并返回null给属性,导致视图识别出错误:
这里用户正在输入一个带有超越函数的方程式(这是在焦点离开TextBox之前):
焦点离开TextBox后,显示如下:
这种方法的主要问题是,如果绑定到数值属性,ViewModel无法知道方程式错误。还有一个问题是,视图也不知道错误,无法禁用继续。这可以通过使数字可空来修复,但然后显示给用户的价值将被重置,用户将无法修复方程式,而且红色错误边框也不会显示。如果知道输入错误很重要,那么要么ViewModel需要增强,需要使用多值转换器,要么将评估移到属性中(在这种情况下,将尝试在属性设置中使用JavaScriptEval()方法评估用户输入),这将是字符串类型。
认为最后一个解决方案是最好的。如果需要在表单上指示错误,那么可以使用值转换器设置一些属性(边框、背景等),如果属性是非数值的,就将属性设置为无效值。ViewModel会知道属性无效,因为它是非数值的。剩下的就是将属性在字符串值和适当的数值之间转换以供模型使用。这需要更多的工作,但对于添加的功能来说并不坏。
目前,值转换器并不智能,如果这个转换器绑定到一个整数类型(例如,Integer)的格式,而Eval()方法返回一个浮点或定点值,它将导致错误。这可以通过添加代码来确定正在绑定的属性的类型,并在返回值之前进行转换来轻松修复。这也将是溢出的问题。