(一). 概述
业余时间做了一个非常有用的控件, 介绍一下. 一般当我们要实现这样一个计算功能页面: 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, 用于编译算法中存储数据结点和字符结点
1 /**/ /// 2 /// Author: [ ChengKing(ZhengJian) ] 3 /// Blog: Http://blog.csdn.net/ChengKing 4 /// 5 /// 6 /// Node 的摘要说明 7 /// 8 /// 9 /// 结点类[操作符结点] 10 /// 11 public class Node 12 { 13 public string str; //存储本节点字串14 public int startIndex; //用于存储一个结点所在[运算表达式]的开始索引位置15 public int endIndex; //用于存储一个结点所在[运算表达式]的结束索引位置 161718 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 代码
1 /// 2 /// Author: [ ChengKing(ZhengJian) ] 3 /// Blog: Http://blog.csdn.net/ChengKing 4 /// 5 [DefaultProperty( " Text " )] 6 [ToolboxData( " <{0}:AutoCompute runat=server> " )] 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脚本生成注册功能.
1 /// 2 /// Author: [ ChengKing(ZhengJian) ] 3 /// Blog: Http://blog.csdn.net/ChengKing 4 /// 5 /// 6 /// ConvertHelper 的摘要说明 7 /// 8 /// 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 /// 18 public class ConvertHelper 19 { 20 /// 21 /// 存放JavaScript运算符的各种结点 22 /// 23 private string [] OP_Chars = new string [ 7 ] { " + " , " - " , " * " , " / " , " ( " , " ) " , " , " }; 24 25 /// 26 /// 自定义变量前缀符号 27 /// 28 private string VarPreSymbol = " _ " ; 29 30 /// 31 /// 存储要读取控件的属性(如: t.text/t.Value etc) 32 /// 33 private string ValueSymbol = " .value " ; 34 35 /// 36 /// 存储compute方法脚本变量 37 /// 38 private string ComputeScript = "" ; 39 40 /// 41 /// 存储onblur方法脚本变量 42 /// 43 private string OnblurScript = "" ; 44 45 /// 46 /// 区别于方法名的序列号[依次递增, 如: compute1, compute2, compute3 ] 47 /// 48 private int SequenceNum = 1 ; 49 50 /// 51 /// 抽取出运算符结点[其中包括运算符结点的位置信息] 52 /// 53 /// 54 /// 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 /// 87 /// 根据运算符结点抽取出数据结点[其中包括数据结点的位置信息] 88 /// 89 /// 90 /// 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 /// 133 /// 判断相邻结点中间是否是数据结点134 /// 135 /// 136 /// 137 /// 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 /// 172 /// //判断是否Math.开头 排除(Math.E/Math.LN10/Math.LN2/Math.LOG10E/Math.LOG2E/ Math.PI/Math.SQRT1_2/Math.SQRT2)等常量173 /// 174 /// 175 /// 176 public bool IsIndexOfMath( string str) 177 { 178 if (str.IndexOf( " Math. " ) == 0 ) 179 { 180 return true ; 181 } 182 return false ; 183 } 184 185 /// 186 /// 判断是否是数字187 /// 188 /// 189 /// 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 /// 215 /// 返回处理后的表达式216 /// 217 /// 218 /// 219 /// 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 /// 226 /// 生成并注册compute方法脚本227 /// 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 /// 272 /// 生成并注册onblur脚本(调用compute方法)273 /// 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 + " ') " + " .οnblur=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 /// 289 /// 重新调整数据节点集合的索引值290 /// 291 /// 292 /// 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( " 括号不匹配! " ); 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 }
(五). 示例代码下载
(六). 其它相关自定义控件文章
(七).此控件的第二个版本源码已经发布, 请看这里, 内容更精彩:
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1562765