【夜战鹰】【ChengKing(ZhengJian)】

【夜战鹰】【ChengKing(ZhengJian)】

博客园 首页 联系 订阅 管理

(一). 概述

         前几天做了一个自定义控件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方法计算, 从程序逻辑讲这是不合理的.

        基于此, 我又采用了新设计方案. 下面就详细说一下新的设计思路. 在看代码以前请先看看新的设计思路文档. 新生成的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 > 1return 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 < 0return 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 + "')" + ".onblur=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 新版本能够增加个类似此功能的控件.

        

(五). 新方案控件源码

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

 

 

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

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








posted on 2007-04-27 22:14  【ChengKing(ZhengJian)】  阅读(1477)  评论(14编辑  收藏  举报