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

2007年04月27日 20:40:00

(一). 概述

前几天做了一个自定义控件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";
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";
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



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1587794


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值