最近开始学习WPF,发现WPF的绑定真好用。对于初学者来说,需要绑定一个通过自定义表达式计算出来的属性值,有点难度。下面是利用IValueConverter实现子控件随窗体大小改变。子控件的长宽=窗体的长宽 * A(0<A<1)。但绑定时,子控件的Binding.Path只能填窗体的Property属性名称,不能实现前面需求。Binding中提供了ConverterParameter可以让我们把表达式功能实现。
控件绑定窗体代码:
col0.SetBinding(ColumnDefinition.WidthProperty, new Binding("ActualWidth") {Mode=BindingMode.OneWay ,Source=this,ConverterParameter= "({0}*0.382)",Converter=new DTEvalConvert() });
或 控件绑定界面代码:
Window标签中引入EvalForValueConverter类
xmlns:eval="clr-namespace:EvalForValueConverter"
转换器设置为页面资源
<Window.Resources>
<eval:DTEvalConvert x:Key="dtvc" />
<eval:JsEvalConvert x:Key="jsvc"/>
<eval:xPathEvalConvert x:Key="xpvc"/>
<eval:AnalyzerEvalConvert x:Key="ayvc"/>
</Window.Resources>
绑定 表达式在ConverterParameter上写,注意加但引号。
<ColumnDefinition Width="{Binding ElementName=meWindow,Path=ActualWidth,Converter={StaticResource jsvc } ,ConverterParameter='({0}*0.382)' }">
界面代码,不含界面绑定代码。
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="18"></Setter>
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0,0,20,0"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="FontSize" Value="16"></Setter>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="20,0,0,0"/>
<Setter Property="Width" Value="180"/>
</Style>
<Style TargetType="Button">
<Setter Property="Margin" Value="10,10,10,10"/>
<Setter Property="Width" Value="80"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="col0">
</ColumnDefinition>
<ColumnDefinition x:Name="col1">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="txtTitle" FontSize="24" HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="0">操 作 标 题</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" >读者编号</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="2" >读者姓名</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="3" >性别</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="4" >年龄</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="5" >联系电话</TextBlock>
<TextBox x:Name="txtID" Grid.Column="1" Grid.Row="1" ></TextBox>
<TextBox x:Name="txtName" Grid.Column="1" Grid.Row="2" ></TextBox>
<StackPanel Grid.Column="1" Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Center" >
<RadioButton x:Name="rdMale" Content="男" Width="80" FontSize="18" Margin="20,0,0,0" ></RadioButton>
<RadioButton x:Name="rbFemale" Content="女" Width="80" FontSize="18"></RadioButton>
</StackPanel>
<TextBox x:Name="txtAge" Grid.Column="1" Grid.Row="4" ></TextBox>
<TextBox x:Name="txtTel" Grid.Column="1" Grid.Row="5" ></TextBox>
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="6" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="btnAdd">添 加</Button>
<Button x:Name="btnRetSet">重 置</Button>
<Button x:Name="btnBack">返 回</Button>
<Button x:Name="btnSave">保 存</Button>
</StackPanel>
</Grid>
4表达式转换器代码
using System;
using System.CodeDom.Compiler;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows.Data;
namespace EvalForValueConverter
{
/// <summary>
/// DataTalbe.Compute实现表达式
/// </summary>
public class DTEvalConvert : IValueConverter
{
static System.Data.DataTable dt = new System.Data.DataTable();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
{
string expression = parameter.ToString().Replace("{0}", value.ToString());
return dt.Compute(expression, "");
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
/// <summary>
/// JScript引擎实现表达式
/// </summary>
public class JsEvalConvert : IValueConverter
{
private static object _evaluator = null;
private static Type _evaluatorType = null;
/// <summary>
/// JScript代码
/// </summary>
private static readonly string _jscriptSource =
@"class Evaluator
{
public function Eval(expr : String) : String
{
return eval(expr);
}
}";
static JsEvalConvert()
{
//构造JScript的编译驱动代码
CodeDomProvider provider = CodeDomProvider.CreateProvider("JScript");
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
CompilerResults results;
results = provider.CompileAssemblyFromSource(parameters, _jscriptSource);
Assembly assembly = results.CompiledAssembly;
_evaluatorType = assembly.GetType("Evaluator");
_evaluator = Activator.CreateInstance(_evaluatorType);
}
/// <summary>
/// 计算结果,如果表达式出错则抛出异常
/// </summary>
/// <param name="statement">表达式,如"1+2+3+4"</param>
/// <returns>结果</returns>
public static object JsEval(string statement)
{
return _evaluatorType.InvokeMember(
"Eval",
BindingFlags.InvokeMethod,
null,
_evaluator,
new object[] { statement }
);
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
{
string expression = parameter.ToString().Replace("{0}", value.ToString());
return JsEval(expression);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
/// <summary>
/// xPath表达式
/// </summary>
public class xPathEvalConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
{
string expression = parameter.ToString().Replace("{0}", value.ToString());
return Evaluate(expression);
}
return value;
}
public static double Evaluate(string expression)
{
return (double)new System.Xml.XPath.XPathDocument(
new StringReader("<r/>")).CreateNavigator().Evaluate
(string.Format("number({0})", new System.Text.RegularExpressions.Regex(@"([\+\-\*])")
.Replace(expression, " ${1} ")
.Replace("/", " div ")
.Replace("%", " mod ")));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
/// <summary>
/// 代码分析表达式
/// </summary>
public class AnalyzerEvalConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
{
string expression = parameter.ToString().Replace("{0}", value.ToString());
return AnalyzerEvaluator.Eval(expression);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
/// <summary>
/// 动态求值
/// </summary>
sealed class AnalyzerEvaluator
{
/**/
/// <summary>
/// 计算结果,如果表达式出错则抛出异常
/// </summary>
/// <param name="statement">表达式,如"1+2+3+4"</param>
/// <returns>结果</returns>
public static object Eval(string statement)
{
if (statement.Trim() != string.Empty)
{
AnalyzerEvaluator evaluator = new AnalyzerEvaluator();
return evaluator.GetFormulaResult(statement);
}
else
{
return null;
}
}
private object GetFormulaResult(string s)
{
if (s == "")
{
return null;
}
string S = BuildingRPN(s);
string tmp = "";
System.Collections.Stack sk = new System.Collections.Stack();
char c = ' ';
System.Text.StringBuilder Operand = new System.Text.StringBuilder();
double x, y;
for (int i = 0;
i < S.Length;
i++)
{
c = S[i];
//added c==',' for germany culture
if (char.IsDigit(c) || c == '.' || c == ',')
{
//数据值收集.
Operand.Append(c);
}
else if (c == ' ' && Operand.Length > 0)
{
#region 运算数转换
try
{
tmp = Operand.ToString();
if (tmp.StartsWith("-"))//负数的转换一定要小心...它不被直接支持.
{
//现在我的算法里这个分支可能永远不会被执行.
sk.Push(-((double)Convert.ToDouble(tmp.Substring(1, tmp.Length - 1))));
}
else
{
sk.Push(Convert.ToDouble(tmp));
}
}
catch
{
return null; //
}
Operand = new System.Text.StringBuilder();
#endregion
}
else if (c == '+'//运算符处理.双目运算处理.
|| c == '-'
|| c == '*'
|| c == '/'
|| c == '%'
|| c == '^')
{
#region 双目运算
if (sk.Count > 0)/*如果输入的表达式根本没有包含运算符.或是根本就是空串.这里的逻辑就有意义了.*/
{
y = (double)sk.Pop();
}
else
{
sk.Push(0);
break;
}
if (sk.Count > 0)
x = (double)sk.Pop();
else
{
sk.Push(y);
break;
}
switch (c)
{
case '+':
sk.Push(x + y);
break;
case '-':
sk.Push(x - y);
break;
case '*':
if (y == 0)
{
sk.Push(x * 1);
}
else
{
sk.Push(x * y);
}
break;
case '/':
if (y == 0)
{
sk.Push(x / 1);
}
else
{
sk.Push(x / y);
}
break;
case '%':
sk.Push(x % y);
break;
case '^'://
if (x > 0)//
{
//我原本还想,如果被计算的数是负数,又要开真分数次方时如何处理的问题.后来我想还是算了吧.
sk.Push(System.Math.Pow(x, y));
//
}
//
else//
{
//
double t = y;
//
string ts = "";
//
t = 1 / (2 * t);
//
ts = t.ToString();
//
if (ts.ToUpper().LastIndexOf('E') > 0)//
{
//
;
//
}
//
}
break;
}
#endregion
}
else if (c == '!')//单目取反. )
{
sk.Push(-((double)sk.Pop()));
}
}
if (sk.Count > 1)
{
return null;//;
}
if (sk.Count == 0)
{
return null;//;
}
return sk.Pop();
}
/**/
/// <summary>
///
/// </summary>
private string BuildingRPN(string s)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder(s);
System.Collections.Stack sk = new System.Collections.Stack();
System.Text.StringBuilder re = new System.Text.StringBuilder();
char c = ' ';
//sb.Replace( " ","" );
//一开始,我只去掉了空格.后来我不想不支持函数和常量能滤掉的全OUT掉.
for (int i = 0;
i < sb.Length;
i++)
{
c = sb[i];
//added c==',' for german culture
if (char.IsDigit(c) || c == ',')//数字当然要了.
re.Append(c);
//if( char.IsWhiteSpace( c )||
char.IsLetter(c);//如果是空白,那么不要.现在字母也不要.
//continue;
switch (c)//如果是其它字符...列出的要,没有列出的不要.
{
case '+':
case '-':
case '*':
case '/':
case '%':
case '^':
case '!':
case '(':
case ')':
case '.':
re.Append(c);
break;
default:
continue;
}
}
sb = new System.Text.StringBuilder(re.ToString());
#region 对负号进行预转义处理.负号变单目运算符求反.
for (int i = 0; i < sb.Length - 1; i++)
if (sb[i] == '-' && (i == 0 || sb[i - 1] == '('))
sb[i] = '!';
//字符转义.
#endregion
#region 将中缀表达式变为后缀表达式.
re = new System.Text.StringBuilder();
for (int i = 0;
i < sb.Length;
i++)
{
if (char.IsDigit(sb[i]) || sb[i] == '.')//如果是数值.
{
re.Append(sb[i]);
//加入后缀式
}
else if (sb[i] == '+'
|| sb[i] == '-'
|| sb[i] == '*'
|| sb[i] == '/'
|| sb[i] == '%'
|| sb[i] == '^'
|| sb[i] == '!')//.
{
#region 运算符处理
while (sk.Count > 0) //栈不为空时
{
c = (char)sk.Pop();
//将栈中的操作符弹出.
if (c == '(') //如果发现左括号.停.
{
sk.Push(c);
//将弹出的左括号压回.因为还有右括号要和它匹配.
break;
//中断.
}
else
{
if (Power(c) < Power(sb[i]))//如果优先级比上次的高,则压栈.
{
sk.Push(c);
break;
}
else
{
re.Append(' ');
re.Append(c);
}
//如果不是左括号,那么将操作符加入后缀式中.
}
}
sk.Push(sb[i]);
//把新操作符入栈.
re.Append(' ');
#endregion
}
else if (sb[i] == '(')//基本优先级提升
{
sk.Push('(');
re.Append(' ');
}
else if (sb[i] == ')')//基本优先级下调
{
while (sk.Count > 0) //栈不为空时
{
c = (char)sk.Pop();
//pop Operator
if (c != '(')
{
re.Append(' ');
re.Append(c);
//加入空格主要是为了防止不相干的数据相临产生解析错误.
re.Append(' ');
}
else
break;
}
}
else
re.Append(sb[i]);
}
while (sk.Count > 0)//这是最后一个弹栈啦.
{
re.Append(' ');
re.Append(sk.Pop());
}
#endregion
re.Append(' ');
return FormatSpace(re.ToString());
//在这里进行一次表达式格式化.这里就是后缀式了.
}
/// <summary>
/// 优先级别测试函数.
/// </summary>
/// <param name="opr"></param>
/// <returns></returns>
private static int Power(char opr)
{
switch (opr)
{
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '%':
case '^':
case '!':
return 3;
default:
return 0;
}
}
/// <summary>
/// 规范化逆波兰表达式.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private static string FormatSpace(string s)
{
System.Text.StringBuilder ret = new System.Text.StringBuilder();
for (int i = 0;
i < s.Length;
i++)
{
if (!(s.Length > i + 1 && s[i] == ' ' && s[i + 1] == ' '))
ret.Append(s[i]);
else
ret.Append(s[i]);
}
return ret.ToString();
}
}
}
不同表达式支持的函数不一致,我这里只测试最简单的4则混合运算 ,不使用函数。
下面是测试代码
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0">利用 DataTalbe.Compute实现表达式 </TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0">MS.JScript的ValueConverter</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0">XmlPath的ValueConverter</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0">代码分析的ValueConverter</TextBlock>
<TextBox x:Name="txt0" Grid.Row="0" Grid.Column="1" Width="100"></TextBox>
<TextBox x:Name="txt1" Grid.Row="1" Grid.Column="1" Width="100"></TextBox>
<TextBox x:Name="txt2" Grid.Row="2" Grid.Column="1" Width="100"></TextBox>
<TextBox x:Name="txt3" Grid.Row="3" Grid.Column="1" Width="100"></TextBox>
<TextBox x:Name="txt" Grid.Row="4" Grid.Column="1" Width="100" Text="(87344/568*(246*{0}+337))/97"></TextBox>
<Button x:Name="btn" Click="btn_Click" Grid.Row="4" Height="40" Width="60">开始测试</Button>
</Grid>
private void btn_Click(object sender, RoutedEventArgs e)
{
int c= 100000,i=0;
int[] array = new int[c];
Random rnd = new Random();
for (i=0; i< c; i++)
{
array[i] = rnd.Next(-9999, 9999);
}
string exp = txt.Text;
DateTime ab = DateTime.Now;
DateTime ae = DateTime.Now;
DTEvalConvert dt = new DTEvalConvert();
ab = DateTime.Now;
for ( i=0;i< c;i++)
{
dt.Convert(array[i], null, exp, null);
}
ae = DateTime.Now;
txt0.Text = (ae.Ticks - ab.Ticks).ToString();
JsEvalConvert js = new JsEvalConvert();
ab = DateTime.Now;
for ( i = 0; i < c; i++)
{
js.Convert(array[i], null, exp, null);
}
ae = DateTime.Now;
txt1.Text = (ae.Ticks - ab.Ticks).ToString();
xPathEvalConvert xp = new xPathEvalConvert();
ab = DateTime.Now;
for (i = 0; i < c; i++)
{
xp.Convert(array[i], null, exp, null);
}
ae = DateTime.Now;
txt2.Text = (ae.Ticks - ab.Ticks).ToString();
AnalyzerEvalConvert al = new AnalyzerEvalConvert();
ab = DateTime.Now;
for (i = 0; i < c; i++)
{
al.Convert(array[i], null, exp, null);
}
ae = DateTime.Now;
txt3.Text = (ae.Ticks - ab.Ticks).ToString();
}
下面是测试结果
利用DataTalbe.Compute | 7430425 | 7520430 | 7660438 |
利用MS.JScript Eval() | 17961028 | 19401109 | 19491115 |
利用XmlPath | 40952342 | 44932570 | 44272532 |
代码分析 | 12270702 | 11330648 | 11460656 |
性能是DataTalbe.Compute效率最高,XmlPath最低。代码分析可能实现的功能比较多效率追不上DataTable.Compute.大家可以找个简单的代码分析类测试一下。
------
后记,本来是在学习控件随窗体大小改变,因为重WinForm过来,原来在绑定Path是Window.Width属性,发现转换器总是执行2次,第二次总是传入最初始值而不是新值。找了2天发现有ActualWidth,ActualHeight属性。改为绑定ActualWidth属性,转换器只执行1次,实现预期效果。