Asp.net 2.0 自定义控件开发[实现自动计算功能(AutoComputeControl)][示例代码下载续][重点推荐控件]

(一). 概述

         前几天做了一个自定义控件AutoComputeControl, 具体请见: 

                  http://blog.csdn.net/ChengKing/archive/2007/04/12/1562765.aspx 

        在读本文章之前请先读一下上面链接所指向的文章.

        此控件在99%情况下, 能够很方便地处理页面上TextBox控件之间的自动计算. 但有一种情况, 它有点不完善.

        举例, 假如要计算这样的表达式, 如图:

       

        从图上可以看出, 要同时计算的两个表达式中, 其中 ID为:price 的TextBox同时参与了两个表达式的运算.

       如果按上个控件的计算方法, 经过编译分析表达式后最终是产生的脚本如下:

      

        由控件自动生成的JavaScript可以看出, 脚本代码能够正确的生成; 但发现一个小问题, price控件同时注册了两个onblur事件, 也就是说当price控件失去焦点时要同时执行方法compute1和compute2, 结果才能够计算正确;  但基于JavaScript语法限制, 当注册我个onblur方法(别的事件也一样)时, 默认最后一个起效, 也就是说在上面的代码中, 当price 控件失去焦点时, 只有compute2方法计算, 从程序逻辑讲这是不合理的.

        先看一下采用新算法生成的客户端代码是什么样的(JavaScript伪代码):

        以上是新算法生成的JS伪代码, 从图有些读者已经能够看出里面的弦机, 事实

上在生成js时, 算法也没有上图看起来这么简单, 有些情况还涉及到无限递归.

下面就详细说一下新的设计思路. 在看代码以前请先看看新的设计思路文档. 新生成

的JS代码也会在下面发布.

(二). 新设计方案文档      

     1). 自动完成组件功能概述

 

u       通过使用场景理解:

1.         举例: 页面上有一组控件, 其中包括: 五个TextBox控件, 并且它们的ID属性依次为: A B C D E; 另外有个表达式控件F(此控件带个表达式属性, 用来建立要计算的控件的值之间的运算关系). 其中表达式属性设置为: A*(B+C) *D*0.98 + E ; 另外本控件还用来存储显示的计算结果.

      

2.         功能描述: 当页面运行时, 修改A B C D E控件值(比如: A=5, B=6, …), 并且A B C D E等其中任一个控件失去焦点时, E控件会根据表达式重新计算更新到最新值.

3.         以上a) b)是用一组控件(只含一组表达式)进行示例, 它还支持页面上同时放置多组表达式控件, 并且组间控件能够进行交叉.

      

4.         运行状态, 支持嵌套表达式运算. 比如TextBox控件中也能够输入表达式(比如: A中输入的不是6, 而是6*(8+2)). 也能够正确计算出结果.

      

u      实现方案概要

运行时只需设置一个属性:  运算表达式字串, 以下简称 Expression.

其中E包括了本自动计算组件所需的两个重要参数条件: 1. 控件的ID;  2. 控件之间的关系运算, 以下就是通过这两个参数条件进行展开运算.

算法概要流程图:

                                                                        (图一)

1.         Expression用编译算法进行扫描, 区分出:

哪些是: 数据结点(用于输入数据的控件, : A B C D ETextBox控件);

哪些是: 运算符(JavaScript中的运算符, : + - * / )

                     并确定每个数据结点在E中的起始/结束位置索引等信息.

2.         根据 a) 得出的数据信息和运算表达式运算关系, C# 动态生成每组控件的计算表达式JavaScrpt代码, 并注册A B C D ETextBox控件的引发事件, 组装成的JavaScript脚本格式如下

      

 1  // 计算表达式代码
 2  < script language = ' javascript ' >  
 3   function compute1() 
 4   { 
 5    var _C  =  parseFloat(eval(document.getElementById( ' C ' ).value));
 6    var _D  =  parseFloat(eval(document.getElementById( ' D ' ).value));
 7    document.getElementById( ' A ' ).value = _C + _D;
 8   }
 9  </ script >
10 
11  < script language = ' javascript ' >  
12   function compute2() 
13   { 
14    var _A  =  parseFloat(eval(document.getElementById( ' A ' ).value));
15    var _E  =  parseFloat(eval(document.getElementById( ' E ' ).value));
16    document.getElementById( ' B ' ).value = _A * _E;
17   }
18  </ script >
19 
20  // 注册引发事件
21  < script language = ' javascript ' >  
22   function onblurA() 
23   { 
24    compute1();
25    compute2();
26   }
27  </ script >
28 

    

                                       (代码一)

 

        注意: 表达式不是固定不变的, 在运行状态, 动态改变表达式字串属性, 动态

         生成的JavaScript也不同. 可以任意改变计算表达式之间的运算关系.

 

    2). 详细设计算法流程图

 

u       编译字符串算法

Ø         得到操作符结点信息(+-*/)

扫描字符串, 当遇到有操作符字符{ "+", "-", "*", "/", "(", ")", "," },

其存储到数组中.

Ø         根据以上得到的操作符信息集合, 得到数据结点信息(A B C D ETextBoxID)

算法规则概述:

       一般情况下, 两个操作符(+-*/)之间的字符串为[数据变量结点(: A B CTextBoxID)], 但下面几种情况要排除:

a)       提取的相邻字符中, 右边字符为"("操作符时, 中间不是数据变量结点.

b)       两个操作符的索引位置相邻时, 其中间没有字符串, 显然也就没有数据变量结点.

c)       数据变量结点必须是字符串变量, 不能为数值字符串(: 568 ).

d)       排除Math.E等常量情况,因为这些常量也满足条件1, 包括(Math.E/Math.LN10/Math.LN2/Math.LOG10E/Math.LOG2E/ Math.PI/Math.SQRT1_2/Math.SQRT2).

 

u       注册生成JavaScript算法

Ø         生成并注册客户端脚本Compute核心方法

根据上一步编译字符串得到的数据结点和运算关系, C#组装生成并注册一系列的Compute方法, 为避免多个表达式之间的冲突, 这里用累计数值的方式, :第一个表达式生成的计算方法为Compute1(){}, 第二个为: Compute2(){}, 第三个为: Compute3(){} .

具体代码格式请看上面的: (代码一).

Ø         生成并注册客户端脚本Onblur方法.

Compute, 也是根据编译字符串得到的数据结点和运算关系表达式来用C#

组装生成客户端能够直接运行的JavaScript脚本. 其中数据结点控件 B 生成的代码格式如下:

 1  < script type = " text/javascript " >
 2  <!--
 3    document.getElementById( ' B ' ).onblur = onblurB;
 4  //  -->
 5  </ script >
 6 
 7  < script language = ' javascript ' >  
 8   function onblurB() 
 9   { 
10    compute1();
11    compute2();
12    compute1();
13   }
14  </ script >
15 
 

                  (代码二)

 

 

这里算法比较复杂, 下面举两个例子描述一下, 请先看一个运行的示例界面:

 

其中左边五个TextBoxID从上到下依次为: A B C D E;

右边的TextBox表示: A的值 = 下面的C的值 + D的值.

                   B的值 = 下面的A的值 * E的值.

                   .

                   .

并且设A B C D E控件根据表达式生成的客户端的计算方法依次为:

ComputeA();ComputeB();ComputeC();ComputeD();ComputeE();

 

1.       具体算法思路, 举例当输入A框值后执行的步骤.

 

*         A控件中输入值后,并让A框失去焦点.

 

*         根据右边的五个表达式可以看出, 如果A的值变了, 则会影响到BC框的值,那么就要重新计算BC的值, 必须执行: ComputeBComputeC.

 

*         依次检查刚刚修改过值的BC. B修改之后, 检查一下右边的表达式, 发现没有包括B的表达式. 则这个B点往下分支循环结束; 检查一下C, 会发现A的表达式中, 应该执行A表达式, 这里有条约束, 由于是A触发的onblur事件(A开始展开循环执行), 不能再继续修改A的值, 也中断循环. 整个循环结束.

 

总结以上执行过程, 是这样的:

     

2.         再举个有些复杂的执行情况, 当输入D框值后执行的步骤.

      

*         D控件输入值, 并且让D控件失去焦点

*         看右边表达式, 如果D值改变会影响到控件A和控件C的值,

则要执行ComputeA()ComputeC()方法, 重新计算AC的值.

*         由于A的值和C的值修改了, 则要继续判断AC影响到哪些控件

的值. 其中, A影响到BC; C影响到A.

AC共影响到A,B,C结点的值, 依次执行 ComputeA(), ComputeB(), ComputeC()方法.

*         再继续判断刚刚值改变的A, B, C的值, 看看 A, B, C分别都会影响

到哪些值改变, 一直遵循这样的规律判断 … … .

                            会发现它一直这样循环下去, 无终结.

总结用图来直观地看一下以上执行过程, 是这样执行的:

为了不让它们限入死循环, 我们限定循环的终止条件有三个:

 

1.         正确结束: 当前结点值改变后不会影响其它任何结点的值改变.

2.         触发起始结点结束: 假如是从A开始的, 即先执行Aonblur(鼠标失去焦点,然后导致触发ComputeA)事件, 则当其它结点再影响到A, 则中断执行.

3.         深度超过3层则强制循环结束.

这里分两种情况:

a)         用户输入的一系列表达式之间是不符合正确逻辑的, :

{

A := B + 1

B := A + 1

                                                 }

                                                 显然程序执行时会使AB不断依次加1, 限入无限死循环

.

b)        用户输入一系列表达式之间是符合正确逻辑的.

有时候也会限入死循环, 但当循环到一定次数时, 由于数据是符合正确逻辑的, 虽然是产生死循环, 但数据是正确的.:

{

A := B + 1

B := A - 1

                                                 }

                                                 这样AB会互相影响, 显然会产生死循环, 依次执行:

                                                 ComputeA();

                                                 ComputeB();

                                                 ComputeA();

                                                 ComputeB();

                                                 ComputeA();

                                                 ComputeB();

                                                 … …

                                                 … …

                                                 但这时数值不会计算错误, 举个实例, 假如在B控件中

                         输入: 5, 则会导致A变为6, 由于A变了6又会导致B

                         为5,一直这样循环下去;  A值永远为6, B值永远为5.

                                                 由此我们控制它最多由上到下执行3.

 

      3). 使用方法

 主类为ConvertHelper.cs , 直接声明实例调用其方法即可. 调用示例: 

 

 1  // 声明实例
 2 
 3          ConvertHelper ch  =   new  ConvertHelper();
 4 
 5   
 6 
 7           ///   <summary>
 8 
 9          /// 注册一系列(这里是五个)表达式, 并生成一系列Compute()方法
10 
11          ///   </summary>   
12 
13       ///   <param name="first"> 当前页面对象 </param>
14 
15          ///   <param name="second"> 控件ID </param>
16 
17          ///   <param name="three">  第second 个参数控件的表达式 </param>
18 
19          ///   <param name="four"> 存放计算结果的控件 </param>
20 
21          ch.RegisterClientScript_Compute( this .Page,  " A " this .Expression1.Text,  " A " );
22 
23          ch.RegisterClientScript_Compute( this .Page,  " B " this .Expression2.Text,  " B " );
24 
25          ch.RegisterClientScript_Compute( this .Page,  " C " this .Expression3.Text,  " C " );
26 
27          ch.RegisterClientScript_Compute( this .Page,  " D " this .Expression4.Text,  " D " );
28 
29          ch.RegisterClientScript_Compute( this .Page,  " E " this .Expression5.Text,  " E " );
30 
31           // 注册所有的Onblur方法
32 
33     ch.RegisterClientScript_Onblur( this .Page);
34 
35 

 

(三). 新方案核心源代码

    1. Node类文件Node.cs, 用于编译算法中存储数据结点和字符结点

     

 1  ///   <summary>
 2       ///  Author: [ ChengKing(ZhengJian) ] 
 3       ///  Blog:   Http://blog.csdn.net/ChengKing
 4       ///   </summary>
 5       ///   <summary>
 6       ///  Node 的摘要说明
 7       ///   </summary>
 8       ///   <summary>
 9       ///  结点类[操作符结点] 
10       ///   </summary>
11       public   class  Node
12      {
13           public   string  str;        // 存储本节点字串
14           public   int  startIndex;    // 用于存储一个结点所在[运算表达式]的开始索引位置
15           public   int  endIndex;      // 用于存储一个结点所在[运算表达式]的结束索引位置    
16 
17 
18           public  Node( int  startIndex,  int  endIndex,  string  str)
19          {
20               this .str  =  str;
21               this .startIndex  =  startIndex;
22               this .endIndex  =  endIndex;        
23          }
24      }
   2. 新增BlurNode类文件BlurNode.cs
 1  ///   <summary>
 2       ///  Author: [ ChengKing(ZhengJian) ] 
 3       ///  Blog:   Http://blog.csdn.net/ChengKing
 4       ///   </summary>
 5       ///   <summary>
 6       ///  用于存储一个数据结点以及与其它表达式之间关系的类
 7       ///   </summary>
 8       class  BlurNode
 9      {
10           public   string  strDataNodeName;  // 存放一个数据结点字串, 如: "price"
11           public   string  strRelationDataNodes;  // 表达式中与之有关联的字串, 如: "price * sum"
12           public   string  strBlurFuncName;  // 存储动态生成的JavaScript焦点失去函数Blur方法名称, 如: Blur_price
13 
14           public   string  strExpression;
15 
16           public  BlurNode()
17          {
18          }
19 
20           public  BlurNode( string  strDataNode,  string  strRelationDataNodes,  string  strBlurFuncName,  string  strExpression)
21          {
22               this .strDataNodeName  =  strDataNode;
23               this .strRelationDataNodes  =  strRelationDataNodes;
24               this .strBlurFuncName  =  strBlurFuncName;
25 
26               this .strExpression  =  strExpression;
27          }
28      }
   3. 主要控件类 AutoCompute.cs 代码
 1  ///   <summary>
 2       ///  Author: [ ChengKing(ZhengJian) ] 
 3       ///  Blog:   Http://blog.csdn.net/ChengKing
 4       ///   </summary>
 5      [DefaultProperty( " Text " )]
 6      [ToolboxData( " <{0}:AutoCompute runat=server></{0}:AutoCompute> " )]
 7       public   class  AutoCompute : Control
 8      {
 9          [Bindable( true )]
10           // [Category("外观")]
11          [DefaultValue( " [AutoCompute / " AutoCompute1/ " ] " )]
12          [Localizable( true )]
13           public   string  Text
14          {
15               get
16              {
17                  String s  =  (String)ViewState[ " Text " ];
18                   return  ((s  ==   null ?  String.Empty : s);
19              }
20 
21               set
22              {
23                  ViewState[ " Text " =  value;
24              }
25          }
26 
27          [Bindable( true )]        
28          [DefaultValue( "" )]
29          [Localizable( true )]
30           public   string  Expression
31          {
32               get
33              {
34                   string  s  =  ( string ) this .ViewState[ " Expression " ];
35                   return  ((s  ==   null ?  String.Empty : s);
36              }
37               set
38              {
39                   this .ViewState[ " Expression " =  value;
40              }
41          }
42 
43           protected   override   void  Render(HtmlTextWriter writer)
44          {
45               if  (DesignMode)
46              {
47                   this .Controls.Clear();
48                  LiteralControl lc  =   new  LiteralControl();
49                  lc.Text  =   this .Text;
50                   this .Controls.Add(lc);
51              }         
52               base .Render(writer);
53          }
54 
55           protected   override   void  OnPreRender(EventArgs e)
56          {
57               base .OnPreRender(e);
58 
59              ConvertHelper _ConvertHelper  =   new  ConvertHelper();
60               string  strClientScript;
61               try
62              {
63                   if  ( this .Expression.Trim().Length  !=   0 )
64                  {
65                       // _ConvertHelper.Main(Page, this.Expression);
66                       // _ConvertHelper.RegisterClientScript(this.Page);
67                      List < Node >  dataNodes  =  _ConvertHelper.BuildDataNode( this .Expression);                   
68                       for  ( int  i  =   0 ; i  <  dataNodes.Count; i ++ )
69                      {
70                          _ConvertHelper.RegisterClientScript_Compute( this .Page, dataNodes[i].str,  this .Expression);
71                      }
72                      _ConvertHelper.RegisterClientScript_Onblur( this .Page);
73                  }
74                   else
75                  {
76                      strClientScript  =   "    alert('No Set [Expression] Property!'); " ;
77                       if  ( ! Page.ClientScript.IsStartupScriptRegistered( " Default_Property " ))
78                      {
79                          Page.ClientScript.RegisterStartupScript( this .GetType(),  " Default_Property " , strClientScript,  true );
80                      }
81                  }
82              }
83               catch
84              {
85                  strClientScript  =   "    alert('The [Expression] format is not correct!'); " ;
86                   if  ( ! Page.ClientScript.IsStartupScriptRegistered( " Default_Property " ))
87                  {
88                      Page.ClientScript.RegisterStartupScript( this .GetType(),  " Default_Property " , strClientScript,  true );
89                  }
90              }
91 
92          }
93 
94      }
4. ConvertHelper.cs类文件, 主要实现编译算法以及JavaScript脚本生成注册功能
  1  ///   <summary>
  2       ///  Author: [ ChengKing(ZhengJian) ] 
  3       ///  Blog:   Http://blog.csdn.net/ChengKing
  4       ///   </summary>
  5       ///   <summary>
  6       ///  ConvertHelper 的摘要说明
  7       ///   </summary>
  8       ///   <summary>
  9       ///  算法概述:
 10       ///   引用概念: [数据变量结点]: 用户命名的字串, 如: total = price*num, 则 "price" 字串就为数据变量结点
 11       ///   1. 抽取出操作运算符. 并记住所有运算符的索引
 12       ///   2. 两个操作符之间的字符串为[数据变量结点(用户命名的字串)], 但下面几种情况要排除:
 13       ///        a. 提取的相邻字符中, 右边字符为"("操作符时, 中间不是数据变量结点.
 14       ///        b. 两个操作符相邻时, 其中间没有字符串, 显然也就没有数据变量结点.
 15       ///        c. 数据变量结点必须是字符串变量, 不能为数值.
 16       ///        d. 排除Math.E等常量情况(Math.E/Math.LN10/Math.LN2/Math.LOG10E/Math.LOG2E/ Math.PI/Math.SQRT1_2/Math.SQRT2).
 17       ///   </summary>
 18       public   class  ConvertHelper
 19      {
 20           ///   <summary>
 21           ///  存放JavaScript运算符的各种结点
 22           ///   </summary>
 23           private   string [] OP_Chars  =   new   string [ 7 ] {  " + " " - " " * " " / " " ( " " ) " " , "  };
 24 
 25           ///   <summary>
 26           ///  自定义变量前缀符号
 27           ///   </summary>
 28           private   string  VarPreSymbol  =   " _ " ;
 29 
 30           ///   <summary>
 31           ///  存储要读取控件的属性(如: t.text/t.Value etc)
 32           ///   </summary>
 33           private   string  ValueSymbol  =   " .value " ;
 34 
 35           ///   <summary>
 36           ///  存储compute方法脚本变量
 37           ///   </summary>   
 38           private   string  ComputeScript  =   "" ;
 39 
 40           ///   <summary>
 41           ///  存储onblur方法脚本变量
 42           ///   </summary>   
 43           private   string  OnblurScript  =   "" ;
 44 
 45           ///   <summary>
 46           ///  区别于方法名的序列号[依次递增, 如: compute1, compute2, compute3  ]
 47           ///   </summary>
 48           private   int  SequenceNum  =   1 ;
 49 
 50           ///   <summary>
 51           ///  表示当前处理哪个控件的Onblur事件
 52           ///   </summary>
 53           private   string  strTheStartOnblurNodeName  =   ""
 54 
 55           ///   <summary>
 56           ///  抽取出运算符结点[其中包括运算符结点的位置信息]
 57           ///   </summary>
 58           ///   <param name="strObject"></param>
 59           ///   <returns></returns>
 60           private  List < Node >  BuildOPNode( string  strObject)
 61          {
 62               int  beginIndex  =   0 // 记录当前处理结点的起始索引       
 63              List < Node >  nodes  =   new  List < Node > ();
 64 
 65 
 66               while  ( true )
 67              {
 68                   if  (beginIndex  ==  strObject.Length)
 69                  {
 70                       break ;
 71                  }
 72 
 73                   for  ( int  j  =   0 ; j  <  OP_Chars.Length; j ++ )
 74                  {
 75                       if  (strObject.Length  -  beginIndex  >=  OP_Chars[j].Length)
 76                      {
 77                           if  (OP_Chars[j]  ==  strObject.Substring(beginIndex, OP_Chars[j].Length))
 78                          {
 79                               // 操作符
 80                              Node node  =   new  Node(beginIndex, beginIndex  +  OP_Chars[j].Length  -   1 , strObject.Substring(beginIndex, OP_Chars[j].Length));
 81                              nodes.Add(node);
 82                               break ;
 83                          }
 84                      }
 85                  }
 86                  beginIndex ++ ;
 87              }
 88               return  nodes;
 89          }
 90 
 91           ///   <summary>
 92           ///  根据运算符结点抽取出数据结点[其中包括数据结点的位置信息]
 93           ///   </summary>
 94           ///   <param name="strObject"></param>
 95           ///   <returns></returns>
 96           public  List < Node >  BuildDataNode( string  strObject)
 97          {
 98               if  (strObject.IndexOf( " = " >=   0 )
 99              {
100                  strObject  =  strObject.Substring( 0 , strObject.IndexOf( " = " )); ;
101              }
102              strObject  =  ClearSpace(strObject);
103              List < Node >  dataNodes  =   new  List < Node > ();
104              List < Node >  opNodes  =   this .BuildOPNode(strObject);
105 
106               // 考虑表达式最左边是数据结点情况, 如: A+B 表达式中的A
107               if  (opNodes.Count  >   0   &&  opNodes[ 0 ].startIndex  !=   0   &&  opNodes[ 0 ].str  !=   " ( " )
108              {
109                   string  str  =  strObject.Substring( 0 , opNodes[ 0 ].startIndex);
110                   if  ( this .JudgeFigure(str)  ==   false   &&   this .IsIndexOfMath(str)  ==   false )
111                  {
112                      Node node  =   new  Node( 0 , opNodes[ 0 ].startIndex  -   1 , str);
113                      dataNodes.Add(node);
114                  }
115 
116              }
117 
118               // 根据操作运算符求得中间的一系列数据结点
119               for  ( int  i  =   0 ; i  <  opNodes.Count  -   1 ; i ++ )
120              {
121                   if  ( this .IsDataNodeBetweenOPNodes(opNodes[i], opNodes[i  +   1 ], strObject))
122                  {
123                      Node node  =   new  Node(opNodes[i].endIndex  +   1 , opNodes[i  +   1 ].startIndex  -   1 , strObject.Substring(opNodes[i].endIndex  +   1 , opNodes[i  +   1 ].startIndex  -  opNodes[i].endIndex  -   1 ));
124                      dataNodes.Add(node);
125                  }
126              }
127 
128               // 考虑最右端是数据结点情况, 如: A+B 表达式中的B
129               if  (opNodes.Count  >   0   &&  (opNodes[opNodes.Count  -   1 ].endIndex  !=  strObject.Length  -   1 ))
130              {
131                   string  str  =  strObject.Substring(opNodes[opNodes.Count  -   1 ].endIndex  +   1 );
132                   if  ( this .JudgeFigure(str)  ==   false   &&   this .IsIndexOfMath(str)  ==   false )
133                  {
134                      Node node  =   new  Node(opNodes[opNodes.Count  -   1 ].endIndex  +   1 , strObject.Length  -   1 , str);
135                      dataNodes.Add(node);
136                  }
137              }
138               return  dataNodes;
139          }
140 
141           ///   <summary>
142           ///  判断相邻结点中间是否是数据结点
143           ///   </summary>
144           ///   <param name="leftNode"></param>
145           ///   <param name="rightNode"></param>
146           ///   <returns></returns>
147           ///   根据以下定理进行判断
148           ///     a. 提取的相邻字符中, 右边字符为"("操作符时, 中间不是数据变量结点.
149           ///     b. 两个操作符相邻时, 其中间没有字符串, 显然也就没有数据变量结点.
150           ///     c. 数据变量结点必须是字符串变量, 不能为数值.
151           ///     d. 排除Math.E等常量情况(Math.E/Math.LN10/Math.LN2/Math.LOG10E/Math.LOG2E/ Math.PI/Math.SQRT1_2/Math.SQRT2).
152           private   bool  IsDataNodeBetweenOPNodes(Node leftNode, Node rightNode,  string  strObject)
153          {
154               // 条件a
155               if  (rightNode.str  ==   " ( " )
156              {
157                   return   false ;
158              }
159 
160               // 条件b
161               if  (leftNode.endIndex  +   1   ==  rightNode.startIndex)
162              {
163                   return   false ;
164              }
165 
166               // 条件c
167               if  ( this .JudgeFigure(strObject.Substring(leftNode.endIndex  +   1 , rightNode.startIndex  -  leftNode.endIndex  -   1 ))  ==   true )
168              {
169                   return   false ;
170              }                   
171              
172               if  ( this .IsIndexOfMath(strObject.Substring(leftNode.endIndex  +   1 , rightNode.startIndex  -  leftNode.endIndex  -   1 )))
173              {
174                   return   false ;
175              }
176 
177               return   true ;
178          }
179 
180           ///   <summary>
181           ///  //判断是否Math.开头 排除(Math.E/Math.LN10/Math.LN2/Math.LOG10E/Math.LOG2E/ Math.PI/Math.SQRT1_2/Math.SQRT2)等常量
182           ///   </summary>
183           ///   <param name="str"></param>
184           ///   <returns></returns>
185           public   bool  IsIndexOfMath( string  str)
186          {
187               if  (str.IndexOf( " Math. " ==   0 )
188              {
189                   return   true ;
190              }
191               return   false ;
192          }
193 
194           ///   <summary>
195           ///  判断是否是数字
196           ///   </summary>
197           ///   <param name="str"></param>
198           ///   <returns></returns>
199           private   bool  JudgeFigure( string  str)
200          {
201               if  (str.Trim().Length  <=   0 )
202                   return   true ;
203               int  dot  =   0 ;
204               if  (str[ 0 ==   ' . '   ||  str[str.Length  -   1 ==   ' . ' )
205                   return   false ;
206               for  ( int  i  =   0 ; i  <  str.Length; i ++ )
207              {
208                   if  (dot  >   1 return   false ;
209                   if  (Char.IsDigit(str, i))
210                  {
211                       continue ;
212                  }
213                   if  (str[i]  ==   ' . ' )
214                  {
215                      dot  =  dot  +   1 ;
216                       continue ;
217                  }
218                   return   false ;
219              }
220               return   true ;
221          }
222 
223           ///   <summary>
224           ///  生成客户端计算表达式的脚本(Compute客户端方法)
225           ///   </summary>
226           ///   <param name="str"></param>
227           ///   <param name="?"></param>
228           ///   <param name="strEnd"> 显示结果的容器 </param>
229           ///   <returns></returns>
230           private   string  CreateClientScript_Compute(Page page,  string  strControl,  string  strExpression, List < Node >  dataNodes,  string  strEnd)
231          {
232               ///   <summary>
233               ///  生成并注册compute方法脚本
234               ///   </summary>
235               int  intNumDataNodeCount  =  dataNodes.Count;
236 
237               // 调整方法名, 防止多个表达式运算时, 方法名冲突
238               while  ( true )
239              {
240                   if  ( ! page.ClientScript.IsClientScriptBlockRegistered( this .GetType(),  " compute "   +  SequenceNum.ToString()))
241                  {
242                       break ;
243                  }
244                  SequenceNum ++ ;
245              }
246 
247               // 生成脚本头JS字串
248               string  strJSHead  =   " <script language='javascript'> /n function compute "   +   this .SequenceNum.ToString()  +   " () /n { /n " ;
249 
250               // 生成脚本体JS字串
251               string  strJSBody  =   "" ;
252               for  ( int  i  =   0 ; i  <  intNumDataNodeCount; i ++ )
253              {                
254                  strJSBody  +=   "   var  "   +  VarPreSymbol  +  dataNodes[i].str  +   "  = parseFloat(eval(document.getElementById(' "   +  ((Control)page.FindControl(dataNodes[i].str)).ClientID  +   " ') "   +  ValueSymbol  +   " ));/n " ;               
255              }
256              
257              strJSBody  +=   "   document.getElementById(' "   +  ((Control)page.FindControl(strEnd)).ClientID  +   " ') "   +  ValueSymbol;
258             
259              strJSBody  +=   " = " ;
260 
261               string  strFormatExpression  =  strExpression;  // 格式化表达式
262               for  ( int  i  =   0 ; i  <  intNumDataNodeCount; i ++ )
263              {
264                  strFormatExpression  =  strFormatExpression.Remove(dataNodes[i].startIndex, dataNodes[i].str.Length);
265                  strFormatExpression  =  strFormatExpression.Insert(dataNodes[i].startIndex,  " _ "   +  dataNodes[i].str);
266                   this .RepairNodes( ref  dataNodes, i  +   1 );
267              }
268              strFormatExpression  +=   " ; " ;
269              strJSBody  +=  strFormatExpression;
270               string  strJSFoot  =   " /n }/n</script>/n/n " ;
271 
272               string  strReturnScript  =  strJSHead  +  strJSBody  +  strJSFoot;
273               this .ComputeScript  =  strReturnScript;
274 
275 
276               #region  存储BurlNodes信息
277 
278               // 存储BlurNodes, 备以后用来生成所有Blur事件
279              List < BlurNode >  list;
280               if  (page.Session[ " BlurNodes " ==   null )
281              {
282                  list  =   new  List < BlurNode > ();
283              }
284               else
285              {
286                  list  =  (List < BlurNode > )page.Session[ " BlurNodes " ];
287              }
288 
289               // 与本结点表达式有关的其它结点集合
290               string  strRelationNodes  =   "" ;
291               for  ( int  i  =   0 ; i  <  intNumDataNodeCount; i ++ )
292              {
293                   if  (i  ==  dataNodes.Count  -   1 )
294                  {
295                      strRelationNodes  +=  dataNodes[i].str;
296                       continue ;
297                  }
298                  strRelationNodes  +=  dataNodes[i].str  +   " , " ;
299              }
300 
301              list.Add( new  BlurNode(strControl, strRelationNodes,  " compute "   +   this .SequenceNum.ToString(), strExpression));
302 
303               // 保存到Session
304               if  (page.Session[ " BlurNodes " !=   null )
305              {
306                  page.Session.Remove( " BlurNodes " );
307              }
308              page.Session[ " BlurNodes " =  list;
309 
310               #endregion
311 
312 
313               // strReturnScript += strOnBlur;
314               return  strReturnScript;
315          }
316 
317 
318           ///   <summary>
319           ///  重新调整数据节点集合的索引值
320           ///   </summary>
321           ///   <param name="nodes"></param>
322           ///   <param name="index"></param>
323           private   void  RepairNodes( ref  List < Node >  nodes,  int  index)
324          {
325               for  ( int  i  =  index; i  <  nodes.Count; i ++ )
326              {
327                   // 6相当于前面数据结点插入的 ".value" 的长度
328                  nodes[i].startIndex  =  nodes[i].startIndex  +  VarPreSymbol.Length;
329                  nodes[i].endIndex  =  nodes[i].endIndex  +  VarPreSymbol.Length;
330              }
331          }
332 
333           ///   <summary>
334           ///  对输入表达式进行验证
335           ///   </summary>
336           ///   <param name="page"></param>
337           ///   <param name="str"></param>
338           ///   <returns></returns>
339           private   bool  ValidateExpression(Page page,  string  str)
340          {
341              str  =   this .ClearSpace(str);
342               if  (CheckParenthesesMatching(str)  ==   false )
343              {
344                  page.Response.Write( " <br><br><br><br><br> 括号不匹配! " );
345                   return   false ;
346              }
347               return   true ;
348          }
349 
350           // 检查括号是否匹配
351           private   bool  CheckParenthesesMatching( string  strCheck)
352          {
353               int  number  =   0 ;
354               for  ( int  i  =   0 ; i  <  strCheck.Length; i ++ )
355              {
356                   if  (strCheck[i]  ==   ' ( ' ) number ++ ;
357                   if  (strCheck[i]  ==   ' ) ' ) number -- ;
358                   if  (number  <   0 return   false ; // 右括号不能在前面
359              }
360               if  (number  !=   0 )
361              {
362                   return   false ;
363              }
364               return   true ;
365          }
366 
367           // 消去空格
368           private   string  ClearSpace( string  str)
369          {
370               return  str.Replace( "   " "" );
371          }
372 
373           ///   <summary>
374           ///  注册客户端脚本_Compute
375           ///   </summary>
376           ///   <param name="page"></param>
377           ///   <param name="str"> 表达式字串 </param>
378           ///   <param name="strEnd"> ///   <param name="strEnd"> 显示结果的容器 </param></param>
379           ///   <returns></returns>
380           public   string  RegisterClientScript_Compute(Page page,  string  strControl,  string  strExpression)
381          {
382              strExpression  =   this .ClearSpace(strExpression);
383               if  ( this .ValidateExpression(page, strExpression)  ==   false )
384              {
385                   return   " 表达式有误! " ;
386              }
387 
388               string  strLeft  =  strExpression.Substring( 0 , strExpression.IndexOf( " = " )); ;
389               string  strRight  =  strExpression.Substring(strExpression.IndexOf( " = " +   1 );
390 
391               string  strReturn  =   this .CreateClientScript_Compute(page, strControl, strLeft, BuildDataNode(strExpression), strRight);
392 
393               if  ( this .ComputeScript.Length  ==   0 )
394              {
395                   return   " 没有可注册的脚本! " ;
396              }
397 
398               if  ( ! page.ClientScript.IsClientScriptBlockRegistered( this .GetType(),  " compute "   +   this .SequenceNum.ToString()))
399              {
400                  page.ClientScript.RegisterClientScriptBlock( this .GetType(),  " compute "   +   this .SequenceNum.ToString(),  this .ComputeScript,  false );
401              }
402 
403               return  strReturn;
404          }
405 
406           ///   <summary>
407           ///  注册所有客户端Onblur脚本
408           ///   </summary>
409           public   void  RegisterClientScript_Onblur(Page page)
410          {
411              List < BlurNode >  list;
412               if  (page.Session[ " BlurNodes " ==   null )
413              {
414                   return ;
415              }
416 
417 
418              list  =  (List < BlurNode > )page.Session[ " BlurNodes " ];
419 
420               foreach  (BlurNode node  in  list)
421              {
422                   string  strOnblurScript  =  CreateClientScript_Onblur(node, list);
423                   if  ( ! page.ClientScript.IsStartupScriptRegistered( this .GetType(),  " onblur_ "   +  node.strDataNodeName))
424                  {
425                      page.ClientScript.RegisterStartupScript( this .GetType(),  " onblur_ "   +  node.strDataNodeName, strOnblurScript,  false );
426                  }
427 
428                   string  strOnBlur  =   "   document.getElementById(' "   +  ((Control)page.FindControl(node.strDataNodeName)).ClientID  +   " ') "   +   " .οnblur=onblur "   +  node.strDataNodeName  +   " ;/n " ;
429                   if  ( ! page.ClientScript.IsStartupScriptRegistered( this .GetType(),  " set_onblur_ "   +  node.strDataNodeName))
430                  {
431                      page.ClientScript.RegisterStartupScript( this .GetType(),  " set_onblur_ "   +  node.strDataNodeName, strOnBlur,  true );
432                  }
433 
434              }
435          }
436 
437           ///   <summary>
438           ///  建立每个TextBox控件的生成脚本方法
439           ///   </summary>  
440           ///   <returns> 每个控件的Onblur事件执行的js脚本 </returns>
441           private   string  CreateClientScript_Onblur(BlurNode currentNode, List < BlurNode >  list)
442          {
443               string  strJSHead  =   " /n<script language='javascript'> /n function onblur "   +  currentNode.strDataNodeName  +   " () /n {  " ;
444               string  strJSBody  =   "" ;    // 生成类似 <<compute1(); compute3>>的字串,要在递归生成,所以定义在方法外面(类变量)
445               string  strJSFoot  =   " /n }/n</script>/n " ;
446 
447               this .strTheStartOnblurNodeName  =  currentNode.strDataNodeName;
448              Queue queue  =   new  Queue();
449              queue.Enqueue(currentNode);  // 把当前对象加入对列
450 
451               /// <summary>
452               ///  循环输出执行结果
453               /// <summary>
454               int  intElementNum  =   0 ;
455               while  (queue.Count  >   0 )
456              {
457                  BlurNode bnFirstNode  =  (BlurNode)queue.Dequeue();
458 
459                   foreach  (BlurNode node  in  list)
460                  {
461                       if  (node.strDataNodeName  !=   this .strTheStartOnblurNodeName)
462                      {
463                           if  (node.strRelationDataNodes.IndexOf(bnFirstNode.strDataNodeName)  >=   0 )
464                          {
465                              strJSBody  +=   " /n   "   +  node.strBlurFuncName  +   " (); " ;
466 
467                               if  (intElementNum  <=  list.Count  *   3 )
468                              {
469                                  queue.Enqueue(node);
470                                  intElementNum ++ ;
471                              }
472                          }
473                      }
474                  }
475              }
476 
477               return  strJSHead  +  strJSBody  +  strJSFoot;
478          }   
479 
480      }
  5. 测试页面文件default.aspx
 1  < body >
 2       < form id = " form1 "  runat = " server " >
 3       < div >
 4           < asp:Panel ID = " Panel1 "  runat = " server "  BackColor = " #FFE0C0 "  Width = " 70% "  Height = " 36% " >        
 5               < br  />
 6               & nbsp;  & nbsp;  & nbsp;  & nbsp; & nbsp; 同时满足: price  *  num1  =  sum1 和 price  *  num2  =  sum2 < br  />
 7               < br  />
 8               < table cellpadding = 0  cellspacing = 0  border = 0  bordercolor = black align  = center bgcolor = " #cccc66 " >
 9                   < tr >
10                       < th >
11                          单价
12                       </ th >
13                       < th >
14                          数量
15                       </ th >
16                       < th style = " width: 158px " >
17                          总额
18                       </ th >
19                   </ tr >
20                   < tr >
21                       < td style = " height: 24px "  rowspan  =   " 2 " >
22                           < asp:TextBox ID = " price "  runat = " server "  ToolTip = " ID: price " ></ asp:TextBox ></ td >                     
23                       < td style = " height: 24px " >
24                           < asp:TextBox ID = " num1 "  runat = " server "  ToolTip = " ID: num1 " ></ asp:TextBox ></ td >
25                       < td style = " width: 158px; height: 24px; " >
26                           < asp:TextBox ID = " sum1 "  runat = " server "  ToolTip = " ID: sum1 " ></ asp:TextBox ></ td >
27                   </ tr >        
28                  
29                   < tr >
30                       < td style = " height: 24px " >                         
31                           < asp:TextBox ID = " num2 "  runat = " server "  ToolTip = " ID: num2 " ></ asp:TextBox >< td style = " height: 24px " >
32                           < asp:TextBox ID = " sum2 "  runat = " server "  ToolTip = " ID: sum2 " ></ asp:TextBox ></ td >
33                       < td style = " width: 158px; height: 24px; " >
34                           < br  />
35                       </ td >
36                   </ tr >     
37                   < tr >
38                       < td style = " height: 24px " >                         
39                           & nbsp; < td style = " height: 24px " >
40                           & nbsp; </ td >
41                       < td style = " width: 158px; height: 24px; " >
42                           < br  />
43                       </ td >
44                   </ tr >             
45               </ table >
46               < br  />
47               < br  />
48               < br  />
49               < br  />
50               < br  />
51               < br  />
52               < br  />
53           </ asp:Panel >
54           & nbsp; & nbsp; & nbsp; < br  />
55           < cc1:AutoCompute ID = " AutoCompute1 "  runat = " server "  Expression = " price*num1=sum1 " >
56           </ cc1:AutoCompute >
57           < br  />
58           < br  />
59           < cc1:AutoCompute ID = " AutoCompute2 "  runat = " server "  Expression = " price*num2=sum2 " >
60           </ cc1:AutoCompute >
61       </ div >
62       </ form >
63  </ body >

 

(四). 总结

          到现在, 本新方案控件

        http://blog.csdn.net/ChengKing/archive/2007/04/27/1587794.aspx

        能够实现所有情况下的自动计算功能.       

        前一个控件:

        http://blog.csdn.net/ChengKing/archive/2007/04/12/1562765.aspx

       也有它的优点, 就是它不会产生无穷递归情况, 效率高, 并且它已经能

        够完成99%情况了, 实现另外1%情况即本文章讲的情况用到场景并

        不是很多.

        因此可以根据实际场景来选择使用这两个控件.

        另外, 由于在ERP等企业管理软件中在下单时, 经常会遇到这样的计

        算场景, 期望Microsoft Asp.net 新版本能够增加个类似此功能的控件.

        

(五). 新方案控件源码

        http://www.cnblogs.com/Files/MVP33650/自动计算控件(增加多表达式不冲突最优化算法).rar

 

(六). 其它相关自定义控件文章

        http://blog.csdn.net/ChengKing/category/288694.aspx

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值