Asp.net 2.0 自定义控件开发[实现自动计算功能(AutoComputeControl)][示例代码下载]
Posted on 2007-12-17 10:14 csdnexpert 阅读(102) 评论(0) 编辑 收藏 举报(一). 概述
业余时间做了一个非常有用的控件, 介绍一下.
一般当我们要实现这样一个计算功能页面:
TextBox1(单价) * TextBox2(数量) = TextBox3(总和);
并且当在TextBox1或TextBox2中输入数据, 鼠标离开时, TextBox3控件能够即时重新计算新值(乘积).
一般我们的做法步骤是:
1. 拖三个控件到页面上, 默认三个TextBox控件ID分别为: TextBox1, TextBox1,
TextBox3.
2. 写个JavaScript 函数, 能够计算 TextBox1和TextBox2的乘积, 并赋值给TextBox3
即时最新值.
<script language='javascript'>
function compute()
{
var _num = parseFloat(document.getElementById('TextBox1').value);
var _price = parseFloat(document.getElementById('TextBox2').value);
document.getElementById('TextBox3').value=_num*_price;
}
</script>
3. 注册TextBox1和TextBox2的onblur事件(TextBox控件失去焦点时触发).
this.TextBox1.Attributes.Add("onblur","compute()");
this.TextBox2.Attributes.Add("onblur","compute()");
OK, 这样固然能够完成. 也存在以下缺点:
1. 不够通用, 如果好多页面都需要这么一个控件, 那得写上面这些代码到所有
页面中. 开发效率不高. 且容易出错.
2. 页面中代码比较乱. 嵌入好多JavaScript代码. 难以维护.
3. 假如运算表达式非常复杂[如: A*B+(B*C)+Math.E ] 每次设置JS也比较麻烦.
基于以上缺陷,
下面是一个通用的自定义控件AutoComputeControl(自动计算), 能够弥补以上缺点,
且具有通用性,
特点如下:
1. 使用简单, 只需设置一个表达式属性Expression, 控件能够自动完成所有JS脚本.
其中表达式由: 运算符和变量组成(控件的ID), 等一下会详细介绍使用方法.
2. 不仅支持简单运算, 更大的优势是支持复杂表达式, 如:
price*(num+2*(3+6))*Math.E = sum , 即
TextBox1*(TextBox2+2*(3+6)) * Math.E =TextBox3
仅通过将控件的: ID和运算符 任意组合成计算表达式赋值给控件Expression属性,
其它的工作由本控件来完成, 本控件能够自动生成所有JS脚本. 并最终在页面客
户端呈现.
3. 另外, 支持Math对象下面的属性和方法:
Math属性: Math.E Math.LN10 Math.LN2 Math.LOG10E Math.LOG2E
Math.PI Math.SQRT1_2 Math.SQRT2
Math方法: Math.abs(x) Math.acos(x) Math.asin(x) Math.atan(x)
Math.atan2(x,y) Math.ceil(x) Math.floor(x) Math.cos(x)
Math.exp(x) Math.log(x) Math.max(x,y) Math.min(x,y)
Math.pow(x,y) Math.random() Math.round(20.49) Math.sin(x)
Math.sqrt(x) Math.tan(x)
例如: Math.sin(Math.sqrt(price1*price2))+Math.E*num=sum
即 Math.sin(Math.sqrt(TextBox1*TextBox2))+Math.E*TextBox3=TextBox4
4. 最终用户还可以在控件中输入表达式:
例如, 用户在TextBox1框中输入: 6*(5+2)
再在TextBox2中输入: 3+Math.E*Math.PI
再把表达式: TextBox1*(TextBox2+2*(3+6)) * Math.E =TextBox3
赋值给本控件属性Expression, 仍然能够正确计算出结果.
5. 在一个页面中放多个本控件.
比如: 拖个本控件到页面跟 TextBox1/TextBox2/TextBox3 组合,
再拖另一个控件跟另一组 TextBox4/TextBox5/TextBox5 组合.
注意: 不要两组表达式产生冲突, 比如把TextBox1即在第一组又在
第二组, 这样脚本生成是正确的, 但这样自动生成的客户端脚本, 会
为TextBox1注册两个onblur事件, 那么默认第二个onblur起效, 不能
同时起效. 这是JavaScript 语法规定的.
实现原理: 本自定义控件的Expression属性, 如: TextBox1*(TextBox2+TexBox3)*0.90=TextBox4
中包括:
1. 控件的ID. 2. 表达式之间的关系.
然后自定义控件内核代码会:
1. 用编译算法扫描表达式属性(Expression)值, 分析运算关系.
2. 根据运算关系动态生成要呈现到客户端的JavaScript, 再最终由自定义控件呈现.
(二). 使用步骤
1. 拖三个TextBox控件到页面上, 如图:
2. 设置TextBox1的ID属性为: price; TextBox2的ID属性为: num; TextBox3的ID属性为: sum.
3. 再添加一个AutoCompute控件(本文章主讲控件), 并设置其: Expression 属性值为:
price * num = sum , 意思是: [单价] * [数量] = [总额]
4. 运行即可. 运行后当输入[单价]和[数量]时, 程式能够自动计算[总额]的值.
测试: 当鼠标从[单价]或[数量]控件失去焦点时, 总额值能够重新被计算显示.
5. 扩展:
a. 您也可以输入更复杂的表达式, 比如从上面DropDownList(测试用的控件)里面随便选几个.
b. 不仅能够计算三个TextBox的表达式(如上), 您还可以添加N个TextBox表达式.
功能比较强大吧 :) :)
(三). 表达式规则:
支持JavaScript运算规则, 并支持Math对象的所有属性和方法.
(四). 核心代码
1. Node类文件Node.cs, 用于编译算法中存储数据结点和字符结点
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. 主要控件类 AutoCompute.cs 代码
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 }
68 else
69 {
70 strClientScript = " alert('No Set [Expression] Property!');";
71 if (!Page.ClientScript.IsStartupScriptRegistered("Default_Property"))
72 {
73 Page.ClientScript.RegisterStartupScript(this.GetType(), "Default_Property", strClientScript, true);
74 }
75 }
76 }
77 catch
78 {
79 strClientScript = " alert('The [Expression] format is not correct!');";
80 if (!Page.ClientScript.IsStartupScriptRegistered("Default_Property"))
81 {
82 Page.ClientScript.RegisterStartupScript(this.GetType(), "Default_Property", strClientScript, true);
83 }
84 }
85
86 }
87
88 }
3. ConvertHelper.cs类文件, 主要实现编译算法以及JavaScript脚本生成注册功能.
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 /// 抽取出运算符结点[其中包括运算符结点的位置信息]
52 /// </summary>
53 /// <param name="strObject"></param>
54 /// <returns></returns>
55 private List<Node> BuildOPNode(string strObject)
56 {
57 int beginIndex = 0; //记录当前处理结点的起始索引
58 List<Node> nodes = new List<Node>();
59
60
61 while (true)
62 {
63 if (beginIndex == strObject.Length)
64 {
65 break;
66 }
67
68 for (int j = 0; j < OP_Chars.Length; j++)
69 {
70 if (strObject.Length - beginIndex >= OP_Chars[j].Length)
71 {
72 if (OP_Chars[j] == strObject.Substring(beginIndex, OP_Chars[j].Length))
73 {
74 //操作符
75 Node node = new Node(beginIndex, beginIndex + OP_Chars[j].Length - 1, strObject.Substring(beginIndex, OP_Chars[j].Length));
76 nodes.Add(node);
77 break;
78 }
79 }
80 }
81 beginIndex++;
82 }
83 return nodes;
84 }
85
86 /// <summary>
87 /// 根据运算符结点抽取出数据结点[其中包括数据结点的位置信息]
88 /// </summary>
89 /// <param name="strObject"></param>
90 /// <returns></returns>
91 public List<Node> BuildDataNode(string strObject)
92 {
93 strObject = ClearSpace(strObject);
94 List<Node> dataNodes = new List<Node>();
95 List<Node> opNodes = this.BuildOPNode(strObject);
96
97 //考虑表达式最左边是数据结点情况, 如: A+B 表达式中的A
98 if (opNodes.Count > 0 && opNodes[0].startIndex != 0 && opNodes[0].str != "(")
99 {
100 string str = strObject.Substring(0, opNodes[0].startIndex);
101 if (this.JudgeFigure(str) == false && this.IsIndexOfMath(str) == false)
102 {
103 Node node = new Node(0, opNodes[0].startIndex - 1, str);
104 dataNodes.Add(node);
105 }
106
107 }
108
109 //根据操作运算符求得中间的一系列数据结点
110 for (int i = 0; i < opNodes.Count - 1; i++)
111 {
112 if (this.IsDataNodeBetweenOPNodes(opNodes[i], opNodes[i + 1], strObject))
113 {
114 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));
115 dataNodes.Add(node);
116 }
117 }
118
119 //考虑最右端是数据结点情况, 如: A+B 表达式中的B
120 if (opNodes.Count > 0 && (opNodes[opNodes.Count - 1].endIndex != strObject.Length - 1))
121 {
122 string str = strObject.Substring(opNodes[opNodes.Count - 1].endIndex + 1);
123 if (this.JudgeFigure(str) == false && this.IsIndexOfMath(str) == false)
124 {
125 Node node = new Node(opNodes[opNodes.Count - 1].endIndex + 1, strObject.Length - 1, str);
126 dataNodes.Add(node);
127 }
128 }
129 return dataNodes;
130 }
131
132 /// <summary>
133 /// 判断相邻结点中间是否是数据结点
134 /// </summary>
135 /// <param name="leftNode"></param>
136 /// <param name="rightNode"></param>
137 /// <returns></returns>
138 /// 根据以下定理进行判断
139 /// a. 提取的相邻字符中, 右边字符为"("操作符时, 中间不是数据变量结点.
140 /// b. 两个操作符相邻时, 其中间没有字符串, 显然也就没有数据变量结点.
141 /// c. 数据变量结点必须是字符串变量, 不能为数值.
142 /// d. 排除Math.E等常量情况(Math.E/Math.LN10/Math.LN2/Math.LOG10E/Math.LOG2E/ Math.PI/Math.SQRT1_2/Math.SQRT2).
143 private bool IsDataNodeBetweenOPNodes(Node leftNode, Node rightNode, string strObject)
144 {
145 //条件a
146 if (rightNode.str == "(")
147 {
148 return false;
149 }
150
151 //条件b
152 if (leftNode.endIndex + 1 == rightNode.startIndex)
153 {
154 return false;
155 }
156
157 //条件c
158 if (this.JudgeFigure(strObject.Substring(leftNode.endIndex + 1, rightNode.startIndex - leftNode.endIndex - 1)) == true)
159 {
160 return false;
161 }
162
163 if (this.IsIndexOfMath(strObject.Substring(leftNode.endIndex + 1, rightNode.startIndex - leftNode.endIndex - 1)))
164 {
165 return false;
166 }
167
168 return true;
169 }
170
171 /// <summary>
172 /// //判断是否Math.开头 排除(Math.E/Math.LN10/Math.LN2/Math.LOG10E/Math.LOG2E/ Math.PI/Math.SQRT1_2/Math.SQRT2)等常量
173 /// </summary>
174 /// <param name="str"></param>
175 /// <returns></returns>
176 public bool IsIndexOfMath(string str)
177 {
178 if (str.IndexOf("Math.") == 0)
179 {
180 return true;
181 }
182 return false;
183 }
184
185 /// <summary>
186 /// 判断是否是数字
187 /// </summary>
188 /// <param name="str"></param>
189 /// <returns></returns>
190 private bool JudgeFigure(string str)
191 {
192 if (str.Trim().Length <= 0)
193 return true;
194 int dot = 0;
195 if (str[0] == '.' || str[str.Length - 1] == '.')
196 return false;
197 for (int i = 0; i < str.Length; i++)
198 {
199 if (dot > 1) return false;
200 if (Char.IsDigit(str, i))
201 {
202 continue;
203 }
204 if (str[i] == '.')
205 {
206 dot = dot + 1;
207 continue;
208 }
209 return false;
210 }
211 return true;
212 }
213
214 /// <summary>
215 /// 返回处理后的表达式
216 /// </summary>
217 /// <param name="str"></param>
218 /// <param name="?"></param>
219 /// <returns></returns>
220 private string CreateClientScript(Page page, string strAll, List<Node> nodes)
221 {
222 string strLeft = strAll.Substring(0, strAll.IndexOf("=")); ;
223 string strRight = strAll.Substring(strAll.IndexOf("=")+1);
224
225 /// <summary>
226 /// 生成并注册compute方法脚本
227 /// </summary>
228 int intNumDataNodeCount = nodes.Count;
229
230 //调整方法名, 防止多个表达式运算时, 方法名冲突
231 while (true)
232 {
233 //bool flag = page.ClientScript.IsClientScriptBlockRegistered("compute" + SequenceNum.ToString());
234 if (!page.ClientScript.IsClientScriptBlockRegistered(this.GetType(), "compute" + SequenceNum.ToString()))
235 {
236 if (!page.ClientScript.IsStartupScriptRegistered(this.GetType(), "onblur" + this.SequenceNum.ToString()))
237 {
238 break;
239 }
240
241 }
242 SequenceNum++;
243 }
244
245 //生成脚本头JS字串
246 string strJSHead = "<script language='javascript'> \n function compute" + this.SequenceNum.ToString() + "() \n { \n";
247 //生成脚本体JS字串
248 string strJSBody = "";
249 for (int i = 0; i < intNumDataNodeCount; i++)
250 {
251 strJSBody += " var " + VarPreSymbol + nodes[i].str + " = parseFloat(document.getElementById('" + ((Control)page.FindControl(nodes[i].str)).ClientID + "')" + ValueSymbol + ");\n";
252 }
253 strJSBody += " document.getElementById('" + ((Control)page.FindControl(strRight)).ClientID + "')" + ValueSymbol;
254 strJSBody += "=";
255
256 for (int i = 0; i < intNumDataNodeCount; i++)
257 {
258 strLeft = strLeft.Remove(nodes[i].startIndex, nodes[i].str.Length);
259 strLeft = strLeft.Insert(nodes[i].startIndex, "_" + nodes[i].str);
260 this.RepairNodes(ref nodes, i + 1);
261 }
262 strLeft += ";";
263 strJSBody += strLeft;
264 string strJSFoot = "\n }\n</script>\n\n";
265
266 string strReturnScript = strJSHead + strJSBody + strJSFoot;
267 this.ComputeScript = strReturnScript;
268
269
270
271 /// <summary>
272 /// 生成并注册onblur脚本(调用compute方法)
273 /// </summary>
274 string strOnBlur = "\n<script language='javascript'>\n";
275 for (int i = 0; i < nodes.Count; i++)
276 {
277 strOnBlur += " document.getElementById('" + ((Control)page.FindControl(nodes[i].str)).ClientID + "')" + ".onblur=compute" + this.SequenceNum.ToString() + ";\n";
278 }
279 strOnBlur += "</script>";
280 this.OnblurScript = strOnBlur;
281
282
283
284 strReturnScript += strOnBlur;
285 return strReturnScript;
286 }
287
288 /// <summary>
289 /// 重新调整数据节点集合的索引值
290 /// </summary>
291 /// <param name="nodes"></param>
292 /// <param name="index"></param>
293 private void RepairNodes(ref List<Node> nodes, int index)
294 {
295 for (int i = index; i < nodes.Count; i++)
296 {
297 //6相当于前面数据结点插入的 ".value" 的长度
298 nodes[i].startIndex = nodes[i].startIndex + VarPreSymbol.Length;
299 nodes[i].endIndex = nodes[i].endIndex + VarPreSymbol.Length;
300 }
301 }
302
303 public string Main(Page page, string strAll)
304 {
305 strAll = this.ClearSpace(strAll);
306 if (CheckParenthesesMatching(strAll) == false)
307 {
308 page.Response.Write("<br><br><br><br><br> 括号不匹配!");
309 return "";
310 }
311
312 string strLeft = strAll.Substring(0, strAll.IndexOf("="));
313
314 string strEndJS_Script = this.CreateClientScript(page, strAll, BuildDataNode(strLeft));
315 return strEndJS_Script;
316 }
317
318 //检查括号是否匹配
319 private bool CheckParenthesesMatching(string strCheck)
320 {
321 int number = 0;
322 for (int i = 0; i < strCheck.Length; i++)
323 {
324 if (strCheck[i] == '(') number++;
325 if (strCheck[i] == ')') number--;
326 if (number < 0) return false;//右括号不能在前面
327 }
328 if (number != 0)
329 {
330 return false;
331 }
332 return true;
333 }
334
335 //消去空格
336 private string ClearSpace(string str)
337 {
338 return str.Replace(" ", "");
339 }
340
341 //注册客户端脚本
342 public bool RegisterClientScript(Page page)
343 {
344 if (this.OnblurScript.Length == 0 || this.ComputeScript.Length == 0)
345 {
346 return false;
347 }
348
349 if (!page.ClientScript.IsClientScriptBlockRegistered("compute" + this.SequenceNum.ToString()))
350 {
351 page.ClientScript.RegisterClientScriptBlock(this.GetType(), "compute", this.ComputeScript, false);
352 }
353
354 if (!page.ClientScript.IsStartupScriptRegistered("onblur" + this.SequenceNum.ToString()))
355 {
356 page.ClientScript.RegisterStartupScript(this.GetType(), "onblur", this.OnblurScript, false);
357 }
358 return true;
359 }
360
361 }
(五). 示例代码下载
https://files.cnblogs.com/MVP33650/自动计算控件.rar
(六). 其它相关自定义控件文章
http://blog.csdn.net/ChengKing/category/288694.aspx
(七).此控件的第二个版本源码已经发布, 请看这里, 内容更精彩:
http://blog.csdn.net/ChengKing/archive/2007/04/27/1587794.aspx
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1562765