IValueConverter表达式4种实现及性能比较

       最近开始学习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.Compute743042575204307660438
利用MS.JScript  Eval()179610281940110919491115
利用XmlPath409523424493257044272532
代码分析122707021133064811460656

性能是DataTalbe.Compute效率最高,XmlPath最低。代码分析可能实现的功能比较多效率追不上DataTable.Compute.大家可以找个简单的代码分析类测试一下。

------

后记,本来是在学习控件随窗体大小改变,因为重WinForm过来,原来在绑定Path是Window.Width属性,发现转换器总是执行2次,第二次总是传入最初始值而不是新值。找了2天发现有ActualWidth,ActualHeight属性。改为绑定ActualWidth属性,转换器只执行1次,实现预期效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值