CSDN专家博客精华版

为人民服务!
  首页  :: 新随笔  :: 管理

(一). 概述        

 业余时间做了一个非常有用的控件, 介绍一下.
         一般当我们要实现这样一个计算功能页面:
                      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/// <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. 主要控件类 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                 }
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  /// <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         /// 抽取出运算符结点[其中包括运算符结点的位置信息]
 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 > 1return 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 < 0return 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