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

    技术2022-05-11  15

    (一). 概述        

     业余时间做了一个非常有用的控件, 介绍一下.         一般当我们要实现这样一个计算功能页面:                      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      }

     

    (五). 示例代码下载

            http://www.cnblogs.com/Files/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


    最新回复(0)