jdjdj...
用webgl+unity3d听我的...
作者大大,有没有这个项目的演示地址呀...
<b><font face="黑体"><font size="5">课程说明</font></font></b> <br/> 大家好,在上一节课程中,我们开始了解了如何在在ASP.NET中使用图形编程的技术。今天我们针对验证码技术深入的了解图形编程在ASP.NET中的应用。<br/> 验证码技术是目前很多WEB程序采用的一种安全防御技术。系统在登录的时候不但要输出用户名和密码,还要额外输入一种随机生成的验证码文本,此时用户需要正确的输入这三个信息才能登录到系统中。<br/> 由于验证码技术能有效的抵御某些黑客攻击,因此得到相当广泛的应用,而且在一些C/S系统中也采用了这种源自WEB开发的技术。<br/><font size="5"><b><font face="黑体">验证码原理</font></b></font> <br/> 在现在的软件运行环境下,安全成为大部分软件必须考虑的问题,黑客无处不在,攻击方式日益丰富,尤其是WEB系统由于其开放性更是遇到严峻的考验,黑客事件层出不穷,造成的损失和影响也不断变大,对此我们软件开发人员需要对此有相当的认识并采取措施抵御各种黑客攻击。<br/><font size="4"><b>枚举字典安全攻击</b></font> <br/> 在各种黑客攻击中,很常见的就是套取用户名和密码,其中很多是采用枚举字典的方式来不断的测试用户名和密码。<br/><img src="http://images.cnblogs.com/cnblogs_com/xdesigner/checkcode.gif" width="671" height="661" border="0" onload="thumbImg(this)" /><br/> 比如某黑客获得一银行账号,然后打开账号的开户银行的网上银行登录界面。分析其中的HTML代码,发现其页面粗制滥造,没有验证码,没有任何安全控制,只要求输入银行账号和取款密码就可以登录。黑客心中大喜,马上写了一个程序,直接调用HTTP协议,使用程序来模拟浏览器向网上银行服务器提交账号和密码尝试登录。由于取款密码是6位阿拉伯数字,因此也就有一百万种组合,黑客的电脑从六个零开始测试一直到六个九,这一定会测试出真正的密码。黑客找到一台宽带高速上网的电脑,运行套取取款密码程序后就忙其他事了,假设这台电脑1秒能测试10个密码,于是花费10万秒的时间肯定能找到密码。10万秒也就是27小时,一天多点的时间,实际上很可能用不了那么长的时间。黑客外头转了一圈回来,发现密码已经找到了,于是马上登录网上银行捞钱,或者伪造一个银行卡去ATM机上提取现金。也就是说黑客最多花了一天时间即可获得数目不可预知的非法收入。<br/><font size="4"><b>验证码防御</b></font> <br/> 网上银行可以有很多手段来抵御黑客攻击,比如使用ActiveX控件代替标准的文本框来输入账号和密码,可以使用USB接口的密码盘来进行数据加密和检测,或者使用一个客户端程序代替浏览器来登录网上银行。但这些是客户端技术,千千万万的黑客可以操着各种手术刀来解剖这些技术,从根本上说客户端技术是不可靠的。<br/> 相对而言采用服务器端技术就比较安全了。比如发现密码连续错误3次即锁定账户,1天后才能登录;也可以使用验证码技术来很大程度的抵御枚举字典套取密码的攻击。<br/> 现有一个新的网上银行,和旧网银差不多,但采用了验证码技术,用户登录时除了要输入账号和取款密码,浏览器还显示一个图片,里面显示了一些潦草的字符,用户需要辨认这些字符然后再输入进去,浏览器向服务器提交表单时会附加用户输入的验证码,服务器接受表单数据后除了校验账号和取款密码后,还要检查验证码是否输入正确,若登录信息校验失败,则服务器端则会提示重新登录,而且还生成包含随机内容的新的验证码,用户在次登录时又得重新识别新的验证码了。<br/> 由于正确的验证码文本是保存在服务器上的,客户端的黑客程序不可能获得,验证码的内容是随机的,黑客程序也无法找到规律,只能辨认从服务器端发出的包含验证码的图片来获得验证码。这里就体现了电脑和人脑的差别了,人脑在图形识别方面远远超过了目前的电脑,服务器端使用一些技术生成的书写潦草,充满随机分布的杂点的图片,人脑是可以相当容易的识别的,但目前的电脑是难以识别的。黑客程序无法识别验证码,只能显示图片让黑客亲自辨认,这时每测试一次密码,黑客都得仔细辨认一下验证码图片,然后手工输入验证码文本。最多要输入一百万次,估计全世界没人会愿意进行这样的工作。这样验证码技术就有效的抵御了这种枚举字典测试密码的安全攻击。此时黑客会转而寻找其他方法,而大量的初级黑客会放弃攻击这个网站。<br/><font size="4"><b>验证码技术概念</b></font> <br/> 验证码技术利用了人脑和电脑之间的差别。<br/> 大家都知道电脑和人脑是存在很大的差别的,电脑很胜任数值运算和精确的逻辑判断,很适合执行那些重复又重复的简单数据处理,但图像识别,模糊逻辑判断,学习和创新能力很差。而人脑正好相反,数值运算不行,但图像识别却很擅长。<br/> 在验证码技术中,有一个很关键的过程就是需要从一个充满随机形状的图片中辨认出验证码文本,这个过程目前的电脑是难以实现的,而对人脑却能相当容易。<br/> 采用电脑难于识别而人脑容易识别的图片,强迫人脑参与安全信息验证过程,就是验证码技术。这里包含验证码文本的图片是验证码媒介。仔细观察,我们可以知道这种验证码媒介具有电脑创建容易识别难的特点,因此类似的我们也可以采用合成语音等其他手段来作为验证码媒介。例如服务器提供一个类似QQ表情的图片,加上噪声,然后让用户判断选择这个图片的表情状态,是哭是笑还是流鼻血,这样也可以当作验证码。<br/> 由于枚举字典安全攻击需要大数量的尝试猜测安全信息,其重复过程可能需要数万甚至数亿次,而验证码技术强迫了人脑参与每一次尝试猜测安全信息,人脑难以胜任长时间高频率的简单重复劳动,因此这就使得枚举字典安全攻击变得不可行,如此应用程序成功的防御了枚举字典安全攻击。<br/><font size="5"><b>ASP.NET<font face="黑体">中使用验证码技术</font></b></font> <br/> 由于验证码技术中服务器程序需要创建验证码图片,里面用到了图形编程,因此本节课程仍然是C#发现之旅的图形编程系列教程。<br/> 根据验证码的原理,我们使用C#在ASP.NET中实现了验证码的功能。<br/><font size="4"><b>checkimage.aspx</b></font> <br/> 首先根据上节课程的内容,我们要创建一个图片服务页面,专门用于提供包含验证码文本的图片,为此我们建立一个 checkimage.aspx 的页面。其HTML代码很简单,只有一行,不输出任何内容。在其Page_Load方法中就有创建验证码图片的过程。<div class="blockcode"><div id="code_-1"><ol><li> <br/></li> <li>// 创建一个包含随机内容的验证码文本 <br/></li> <li>System.Random rand = new Random(); <br/></li> <li>int len = rand.Next(4 , 6 ); <br/></li> <li>char[] chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(); <br/></li> <li>System.Text.StringBuilder myStr = new System.Text.StringBuilder(); <br/></li> <li>for( int iCount = 0 ; iCount < len ; iCount ++ ) <br/></li> <li>{ <br/></li> <li> myStr.Append( chars[ rand.Next( chars.Length )]); <br/></li> <li>} <br/></li> <li>string text = myStr.ToString(); <br/></li> <li>// 保存验证码到 session 中以便其他模块使用 <br/></li> <li>this.Session["checkcode"] = text ; <br/></li> <li>Size ImageSize = Size.Empty ; <br/></li> <li>Font myFont = new Font("MS Sans Serif" , 20 ); <br/></li> <li>// 计算验证码图片大小 <br/></li> <li>using( Bitmap bmp = new Bitmap( 10 , 10 )) <br/></li> <li>{ <br/></li> <li> using( Graphics g = Graphics.FromImage( bmp )) <br/></li> <li> { <br/></li> <li> SizeF size = g.MeasureString( text , myFont , 10000 ); <br/></li> <li> ImageSize.Width = ( int ) size.Width + 8 ; <br/></li> <li> ImageSize.Height = ( int ) size.Height + 8 ; <br/></li> <li> } <br/></li> <li>} <br/></li> <li>// 创建验证码图片 <br/></li> <li>using( Bitmap bmp = new Bitmap( ImageSize.Width , ImageSize.Height )) <br/></li> <li>{ <br/></li> <li> // 绘制验证码文本 <br/></li> <li> using( Graphics g = Graphics.FromImage( bmp )) <br/></li> <li> { <br/></li> <li> g.Clear( Color.White ); <br/></li> <li> using( StringFormat f = new StringFormat()) <br/></li> <li> { <br/></li> <li> f.Alignment = StringAlignment.Near ; <br/></li> <li> f.LineAlignment = StringAlignment.Center ; <br/></li> <li> f.FormatFlags = StringFormatFlags.NoWrap ; <br/></li> <li> g.DrawString( <br/></li> <li> text , <br/></li> <li> myFont , <br/></li> <li> Brushes.Black , <br/></li> <li> new RectangleF( <br/></li> <li> 0 , <br/></li> <li> 0 , <br/></li> <li> ImageSize.Width , <br/></li> <li> ImageSize.Height ), <br/></li> <li> f ); <br/></li> <li> }//using <br/></li> <li> }//using <br/></li> <li> // 制造噪声 杂点面积占图片面积的 30% <br/></li> <li> int num = ImageSize.Width * ImageSize.Height * 30 / 100 ; <br/></li> <li> for( int iCount = 0 ; iCount < num ; iCount ++ ) <br/></li> <li> { <br/></li> <li> // 在随机的位置使用随机的颜色设置图片的像素 <br/></li> <li> int x = rand.Next( ImageSize.Width ); <br/></li> <li> int y = rand.Next( ImageSize.Height ); <br/></li> <li> int r = rand.Next( 255 ); <br/></li> <li> int g = rand.Next( 255 ); <br/></li> <li> int b = rand.Next( 255 ); <br/></li> <li> Color c = Color.FromArgb( r , g , b ); <br/></li> <li> bmp.SetPixel( x , y , c ); <br/></li> <li> }//for <br/></li> <li> // 输出图片 <br/></li> <li> System.IO.MemoryStream ms = new System.IO.MemoryStream(); <br/></li> <li> bmp.Save( ms , System.Drawing.Imaging.ImageFormat.Png ); <br/></li> <li> this.Response.ContentType = "image/png"; <br/></li> <li> ms.WriteTo( this.Response.OutputStream ); <br/></li> <li> ms.Close(); <br/></li> <li>}//using <br/></li> <li>myFont.Dispose(); <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_-1'));">复制代码</em></div>首先我们使用.NET框架中随机数生成器 Random类型来生成一个不定长的包含随机数字和英文字符的文本,这就是验证码原始文本,我们将其保存在session中供以后使用。<br/> 然后我们创建一个临时图片,并据此创建一个临时的图象绘制对象,然后调用Graphics的MeasureString函数获得这个字符串的显示大小。据此我们就可以计算出验证码图片的大小。<br/> 然后我们创建一个位图对象,在此基础上创建一个图形绘制对象,然后调用图形绘制对象的DrawString函数将验证码文本绘制在这个位图上。<br/> 绘制验证码后我们在图片上随机的制造杂点来混淆图片内容。这些杂点的面积占图片面积的30%,而且其位置和颜色都是随机的。这些杂点能严重的干扰程序辨认验证码文本。但人脑在辨认文本时能比较轻松的排除这些干扰。<br/> 图片生成后页面就使用PNG格式将图片文档发送到客户端。<br/>checkimage.aspx还提供了一个静态函数来检测验证码。<div class="blockcode"><div id="code_0"><ol><li> <br/></li> <li>/// <summary> <br/></li> <li>/// 检查指定的文本是否匹配验证码 <br/></li> <li>/// </summary> <br/></li> <li>/// <param name="text">要判断的文本</param> <br/></li> <li>/// <returns>是否匹配</returns> <br/></li> <li>public static bool CheckCode( string text ) <br/></li> <li>{ <br/></li> <li> string txt = System.Web.HttpContext.Current.Session["checkcode"] as string ; <br/></li> <li> return text == txt ; <br/></li> <li>} <br/></li> </ol></div><em onclick="copycode($('code_0'));">复制代码</em></div>代码很简单。就是看看参数传进的文本是否等于 session 中保存的验证码文本。其他的页面程序调用这个函数就可以判断验证码的正确性。<br/>login.aspx <br/> 验证码图片服务页面完成后,我们就可以利用这个页面来实现验证码技术。我们建立一个模拟系统登录的页面。<br/><br/><img src="http://images.cnblogs.com/cnblogs_com/xdesigner/checkcodeui.gif" width="447" height="191" border="0" onload="thumbImg(this)" /><br/> 上面放置输入用户名,密码和验证码的三个文本输入框。其中验证码输入框后面放置一个图片,图片就来源于checkimage.aspx页面。用户输入三个信息后点击确定按钮进行登录。则运行该按钮的服务器段代码。<div class="blockcode"><div id="code_1"><ol><li> <br/></li> <li>private void cmdOK_Click(object sender, System.EventArgs e) <br/></li> <li>{ <br/></li> <li> string UserName = this.txtUserName.Text ; <br/></li> <li> string Password = this.txtPassword.Text ; <br/></li> <li> string CheckCode = this.txtCheckCode.Text ; <br/></li> <li> if( UserName == "张三" <br/></li> <li> && Password == "abc" <br/></li> <li> && checkimage.CheckCode( CheckCode ) ) <br/></li> <li> { <br/></li> <li> this.lblResult.Text = "<b>登录成功</b>"; <br/></li> <li> this.RegisterStartupScript("a" , "<script>alert('登录成功');</script>"); <br/></li> <li> } <br/></li> <li> else <br/></li> <li> { <br/></li> <li> this.lblResult.Text = "<font color=red><b>用户登录信息错误,请重新输入</b></font>"; <br/></li> <li> } <br/></li> <li>} <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_1'));">复制代码</em></div>在该代码中,程序获得用户输入的用户名,密码和验证码,然后判断用户名密码是否正确,还调用checkimage的静态函数CheckCode来判断验证码是否正确。只有这三个信息都正确则登录成功,否则登录失败。<br/> 在少数情况下,程序生成的验证码图片难以辨认,则需要重新提供新的验证码图片,此时我们在登录页面中可以双击这个图片来更新验证码图片。显示验证码图片的HTML代码片断为<div class="blockcode"><div id="code_2"><ol><li> <br/></li> <li><img src="checkimage.aspx" <br/></li> <li> title='看不清楚,双击图片换一张。' <br/></li> <li> ondblclick="this.src = 'checkimage.aspx?flag=' + Math.random() " <br/></li> <li> border="1"> <br/></li> </ol></div><em onclick="copycode($('code_2'));">复制代码</em></div>可以看到 ondblclick 事件处理中更新了图片来源,这里使用了一个毫无意义的flag页面参数,这是保证浏览器不会使用本地缓存的验证码图片而是下载最新的验证码图片。<br/> 用户双击图片后,浏览器重新调用checkimage.aspx页面,于是服务器端的验证码文本用了新的,而图片内容也随之更新。<br/> 由于每次尝试登录或更换验证码图片时,正确的验证码都是随机的发生改变,毫无规律,这样就很大的增强了登录页面的安全性。但这样做会让用户登录时需要辨认和输入验证码,这会降低应用程序的可用性。因此是否使用验证码技术是需要多方面权衡的。<br/>小结 <br/> 在本次课程中,我们一起研究了验证码技术的原理,并使用C#在ASP.NET中实现了简单的验证码技术。验证码技术是一种安全防御技术,其中使用了一定的图形编程。这样看来图形编程应用是广泛的,可以为很多其他的技术提供支持。<br/><br/>作者:袁永福
为了让大家更深入的了解和使用C#,我们将开始这一系列的主题为“C#发现之旅”的技术讲座。考虑到各位大多是进行WEB数据库开发的,而所谓发现就是发现我们所不熟悉的领域,因此本系列讲座内容将是C#在WEB数据库开发以外的应用。目前规划的主要内容是图形开发和XML开发,并计划编排了多个课程。在未来的C#发现之旅中,我们按照由浅入深,循序渐进的步骤,一起探索和发现C#的其他未知的领域,更深入的理解和掌握使用C#进行软件开发,拓宽我们的视野,增强我们的软件开发综合能力。<br/>本系列课程配套的演示代码下载地址为 <a href="http://www.51aspx.com/CV/CSharpDiscovery" target="_blank">http://www.51aspx.com/CV/CSharpDiscovery</a>。<br/><br/><font face="黑体"><b><font size="5">课程说明</font></b></font> <br/> 经过以前的课程,大家都掌握了一些图形编程技术,现在我们可以将把图形编程技术投入到实际应用中去。在本课程中,我们一起研究使用图形编程在ASP.NET中实现一个带超链接的饼图。<br/><font size="5"><b><font face="黑体">功能需求</font></b></font> <br/> 现客户提出如下需求,<br/><ul type=1 class="litype_1"><li>在ASP.NET的程序中显示一个二维的椭圆形的饼图界面。<li>其中每一个饼块具有提示文本和一个超链接。<li>当用户鼠标移动到某个饼块时浏览器会显示一些提示文本。<li>当用户点击该饼块则链接到其他页面使得页面能显示和该饼块相关的详细信息。</ul> 该软件的用户界面如图所示<br/><img src="http://bbs.51aspx.com/upload/auto/2_080819100814781.jpg" width="487" height="466" border="0" onload="thumbImg(this)" /><br/><font size="5"><b><font face="黑体">运行软件</font></b></font> <br/> 根据软件功能需求,我已经开发出了这个软件,首先演示运行一下这个软件,给大家一个直观的了解。<br/> 演示程序中有一个pie.aspx的页面。该页面就包含了一个带超链接的饼图对象,在浏览器中打开这个页面,可以看到如下的用户界面<br/><img src="http://bbs.51aspx.com/upload/auto/2_080819100814781.jpg" width="487" height="466" border="0" onload="thumbImg(this)" /><br/> 这个页面中显示一个饼图,每一个扇型都显示数据库中一个客户的总订单金额,总共显示了10个客户。我们鼠标移动到一个饼图项目上,可以显示出这个客户的名称和订单金额。当鼠标点击饼图项目时,就会链接到另外一个页面。该页面也显示一个饼图,其中的项目用于显示单个订单的金额。鼠标放在某个项目上可以看到订单的时间,地点,订单人姓名和订单金额。<br/><img src="http://bbs.51aspx.com/upload/auto/2_080819100822375.jpg" width="461" height="483" border="0" onload="thumbImg(this)" /><br/> 在这里,第一个页面为主页面,第二个页面为明细页面。我们察看主页面的HTML代码,可以看到代码为<div class="blockcode"><div id="code_-1"><ol><li><img src='pieimage.aspx?name=pie_customers' usemap='#dea88afea25748bda1ae28aa3e260d1a' <br/></li> <li> border='0' /> <br/></li> <li><map name="dea88afea25748bda1ae28aa3e260d1a"> <br/></li> <li> <area shape="poly" coords="200,150,399,145,400,150," href="pie_orders.aspx?customerid=ALFKI" <br/></li> <li> title="客户名称:三川实业有限公司订单金额:518.7 点击察看该客户订单的详细情况" /> <br/></li> <li> <area shape="poly" coords="200,150,397,125,399,135,399,145," href="pie_orders.aspx?customerid=ANATR" <br/></li> <li> title="客户名称:东南实业订单金额:2581.95 点击察看该客户订单的详细情况" /> <br/></li> <li> <area shape="poly" coords="200,150,372,73,376,79,380,85,384,92,387,98,390,105,393,111,395,118,397,125," <br/></li> <li> href="pie_orders.aspx?customerid=ANTON" title="客户名称:坦森行贸易订单金额:7023.97 点击察看该客户订单的详细情况" /> <br/></li> <li> <area shape="poly" coords="200,150,269,9,285,14,300,20,314,27,328,34,340,43,352,52,362,62,372,73," <br/></li> <li> href="pie_orders.aspx?customerid=AROUT" title="客户名称:国顶有限公司订单金额:12899.15 点击察看该客户订单的详细情况" /> <br/></li> <li> </map><br/></li> </ol></div><em onclick="copycode($('code_-1'));">复制代码</em></div>我们使用图片来显示饼图图形,使用map元素来在图片上设置提示文本和超链接,使用多边形来模拟扇形的超链接区域。明细页面也类似。<br/><font size="5"><b><font face="黑体">软件设计</font></b></font><br/><font size="4"><b>文档对象模型</b></font><br/> 饼图是一种很常见的展示数据的方式,使用文档对象模型的软件设计思想来分析这个饼图形状,可以很容易的知道,饼图可以抽象为饼图对象本身和饼图项目。<br/> 饼图对象可以定义整体饼图的位置和大小,它的边界就是包含整个饼图的椭圆的外切矩形,而且饼图对象是个容器,可以容纳若干个饼图项目。<br/> 饼图项目定义单个扇形区域,包括饼图项目对应的数值,提示文本,超链接地址,扇形区域的起始角度和终止角度。<br/><font size="4"><b>程序结构设计</b></font> <br/> 该软件应用到ASP.NET中,在桌面程序和WEB程序中应用图形编程具有很大的差别,WEB程序没有视图控件的概念,通常是采用服务器端生成包含图形的图片文档来显示图形,由于WEB程序底层的HTTP传输协议是无状态的,因此程序结构比较复杂,其结构如图所示<br/> <img src="http://bbs.51aspx.com/upload/auto/2_080819100822921.gif" width="673" height="389" border="0" onload="thumbImg(this)" /><br/> 在这里,采用服务器端生成包含图形的图片来显示图形。由于HTML页面是纯文本的文档,不能包含图片文档等二进制数据,因此必须采用至少两个页面来配合显示包含图文的页面,其中主页面来显示HTML文档,而图片服务页面则专门来生成图片文档。<br/> 在这里说明一下程序的流程,首先客户端浏览器向主页面发出请求,该页面中需要显示图形,于是生成一个图形文档对象,向这个图形文档对象填充数据,然后根据图形文档对象和图片服务页面的接口来生成专门显示图片的HTML代码,同时还得讲图片文档对象放入到一个临时数据容器中,最常用的就是session容器。<br/> 主页面处理完成后将生成的HTML文档发送到客户端浏览器,浏览器解析HTML 文档,并根据其引用的图片URL向服务器端的图片服务页面发送请求,最常见的就是IMG元素的SRC属性。图片服务页面根据URL中的参数从临时数据容器中获得图形文档对象,从中获得图像数据,然后转发到浏览器,浏览器获得图像数据后才能完整的展示主页面生成的HTML文档。<br/> 整个过程比较复杂,需要对HTTP和HTML有比较深的了解。<br/> 当然我们可以去掉图片服务页面,在主页面中生成图片,然后将图片保存到一个临时文件中,这样做不甚合理,需要开放WEB程序访问服务器文件系统的权限,而且图片文件是比较大的,占磁盘空间,需要及时删除,而如何删除这些临时文件也是一个问题。而采用图片服务页面是在内存中生成图像文档的,不用写临时文件,这样做方便网站的维护。<br/><font size="5"><b><font face="黑体">软件代码说明</font></b></font> <br/> 根据软件设计,使用VS.NET2003开发出这样的程序,现对该程序代码进行详细说明。首先说明一下饼图文档对象模型。<br/><font size="4"><b>饼图项目对象 PieShapeItem</b></font> <br/> 本类型定义了饼图中的一个项目,代码如下<div class="blockcode"><div id="code_0"><ol><li>/// <summary> <br/></li> <li>/// 单个饼图形状项目 <br/></li> <li>/// </summary> <br/></li> <li>/// <remarks>该类型是PieShape列表的成员类型</remarks> <br/></li> <li>[System.Serializable()] <br/></li> <li>public class PieShapeItem <br/></li> <li>{ <br/></li> <li> private double dblValue = 0 ; <br/></li> <li> /// <summary> <br/></li> <li> /// 数值 <br/></li> <li> /// </summary> <br/></li> <li> public double Value <br/></li> <li> { <br/></li> <li> get{ return dblValue ;} <br/></li> <li> set{ dblValue = value;} <br/></li> <li> } <br/></li> <li> private string strText = null; <br/></li> <li> /// <summary> <br/></li> <li> /// 对象文本 <br/></li> <li> /// </summary> <br/></li> <li> public string Text <br/></li> <li> { <br/></li> <li> get{ return strText ;} <br/></li> <li> set{ strText = value;} <br/></li> <li> } <br/></li> <li> private string strLink = null; <br/></li> <li> /// <summary> <br/></li> <li> /// 项目链接地址 <br/></li> <li> /// </summary> <br/></li> <li> public string Link <br/></li> <li> { <br/></li> <li> get{ return strLink ;} <br/></li> <li> set{ strLink = value;} <br/></li> <li> } <br/></li> <li> private Color intColor = Color.Black ; <br/></li> <li> /// <summary> <br/></li> <li> /// 项目颜色 <br/></li> <li> /// </summary> <br/></li> <li> public Color Color <br/></li> <li> { <br/></li> <li> get{ return intColor ;} <br/></li> <li> set{ intColor = value;} <br/></li> <li> } <br/></li> <li> /// <summary> <br/></li> <li> /// 开始角度 <br/></li> <li> /// </summary> <br/></li> <li> internal float StartAngle = 0 ; <br/></li> <li> /// <summary> <br/></li> <li> /// 结束角度 <br/></li> <li> /// </summary> <br/></li> <li> internal float EndAngle = 0 ; <br/></li> <li>}//public class PieShapeItem<br/></li> </ol></div><em onclick="copycode($('code_0'));">复制代码</em></div>这个代码很简单,也就是定义了一个饼图项目的一些属性。<font size="4"><b>饼图文档对象 PieShape</b></font> <br/> 本对象定义了一个完整的饼图,是本程序的核心模块。本对象首先是PieShapeItem的一个强类型列表,它是从System.Collections.CollectionBase上面派生的,通过使用对象的Add或Remove方法就能往这个对象添加或删除饼图项目对象PieShapeItem。当向列表添加项目时未指定项目颜色时会创建一个默认颜色,本类型定义了一个StdColors的静态的颜色数组,新饼图元素的颜色就根据这个颜色数组进行分配。<br/> 对象定义了一个RefreshState方法用来计算各个饼图元素对应的扇形区域的起始角度和终止角度。<div class="blockcode"><div id="code_1"><ol><li> <br/></li> <li>/// <summary> <br/></li> <li>/// 刷新对象状态 <br/></li> <li>/// </summary> <br/></li> <li>/// <remarks> <br/></li> <li>/// 本函数中反向遍历所有的饼图项目, <br/></li> <li>/// 计算各个饼图项目的起始和终止角度</remarks> <br/></li> <li>public void RefreshState() <br/></li> <li>{ <br/></li> <li> double TotalValue = 0 ; <br/></li> <li> foreach( PieShapeItem item in this ) <br/></li> <li> { <br/></li> <li> TotalValue += item.Value ; <br/></li> <li> } <br/></li> <li> float AngleCount = 0 ; <br/></li> <li> for( int iCount = this.Count - 1 ; iCount >= 0 ; iCount -- ) <br/></li> <li> { <br/></li> <li> PieShapeItem item = this[ iCount ] ; <br/></li> <li> float angle = ( float ) ( 360.0 * item.Value / TotalValue ) ; <br/></li> <li> item.StartAngle = ( float ) Math.Round( AngleCount , 3 ) ; <br/></li> <li> item.EndAngle = ( float ) Math.Round( AngleCount + angle , 3 ) ; <br/></li> <li> AngleCount += angle ; <br/></li> <li> item.StartAngle = ( float ) Math.Round( FixAngle( item.StartAngle ) , 3 ); <br/></li> <li> item.EndAngle = ( float ) Math.Round( FixAngle( item.EndAngle ) , 3 ) ; <br/></li> <li> } <br/></li> <li>} <br/></li> <li>/// <summary> <br/></li> <li>/// 根据椭圆形状修正角度 <br/></li> <li>/// </summary> <br/></li> <li>/// <param name="angle">原始角度</param> <br/></li> <li>/// <returns>修正后的角度值</returns> <br/></li> <li>private float FixAngle( float angle ) <br/></li> <li>{ <br/></li> <li> if( ( angle % 90.0 ) == 0 ) <br/></li> <li> return angle ; <br/></li> <li> if( intWidth == intHeight ) <br/></li> <li> return angle ; <br/></li> <li> double x = intWidth * Math.Cos( angle * Math.PI / 180 ); <br/></li> <li> double y = intHeight * Math.Sin( angle * Math.PI / 180 ); <br/></li> <li> float result = ( float ) ( Math.Atan2( y , x ) * 180 / Math.PI ); <br/></li> <li> if( result < 0 ) <br/></li> <li> result += 360 ; <br/></li> <li> return result ; <br/></li> <li>} <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_1'));">复制代码</em></div>在这个方法中,我们首先计算所有项目的数值和。然后遍历所有的项目,计算它的扇形区域的起始角度和终止角度。由于要显示的是椭圆形饼图,相对于正圆图形是被压扁的,因此这个起始角度和终止角度需要调用FixAngle函数进行修正。<br/> 由于在计算机屏幕上绘制扇形是顺时针方向的,而我们察看一般习惯是顺时针看的,因此这里需要反向遍历饼图项目。<br/> 计算各个饼图项目的角度数据后,我们可以绘制图形或者获得图形定位信息。首先我们定义函数CreatePath函数来获得指定饼图项目的路径对象。代码如下。<div class="blockcode"><div id="code_2"><ol><li> <br/></li> <li>/// <summary> <br/></li> <li>/// 为一个饼图项目创建路径对象 <br/></li> <li>/// </summary> <br/></li> <li>/// <param name="item">饼图项目</param> <br/></li> <li>/// <returns>创建的路径对象</returns> <br/></li> <li>public GraphicsPath CreatePath( PieShapeItem item ) <br/></li> <li>{ <br/></li> <li> GraphicsPath path = new GraphicsPath(); <br/></li> <li> path.AddPie( <br/></li> <li> intLeft , <br/></li> <li> intTop , <br/></li> <li> intWidth , <br/></li> <li> intHeight , <br/></li> <li> item.StartAngle , <br/></li> <li> item.EndAngle - item.StartAngle ); <br/></li> <li> return path ; <br/></li> <li>} <br/></li> <li> 这个函数也很简单,创建一个路径对象,然后添加一个扇形区域。<br/></li> <li> 我们定义了一个Draw函数来绘制饼图图形。其代码为<br/></li> <li>/// <summary> <br/></li> <li>/// 绘制饼图图形 <br/></li> <li>/// </summary> <br/></li> <li>/// <param name="g">图形绘制对象</param> <br/></li> <li>/// <param name="ClipRectangle">剪切矩形</param> <br/></li> <li>public void Draw( Graphics g , Rectangle ClipRectangle ) <br/></li> <li>{ <br/></li> <li> foreach( PieShapeItem item in this ) <br/></li> <li> { <br/></li> <li> using( GraphicsPath path = CreatePath( item )) <br/></li> <li> { <br/></li> <li> using( SolidBrush b = new SolidBrush( item.Color )) <br/></li> <li> { <br/></li> <li> g.FillPath( b , path ); <br/></li> <li> g.DrawPath( Pens.Black , path ); <br/></li> <li> } <br/></li> <li> } <br/></li> <li> } <br/></li> <li>} <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_2'));">复制代码</em></div>这个方法也不复杂,也就是遍历所有的饼图项目,为每一个项目创建路径对象,然后使用饼图项目的颜色填充路径,并使用黑线绘制路径的边框。<br/> 由于本程序是ASP.NET程序,需要在服务器端生成图片文档,因此本对象也提供了CreateBitmap函数来生成包含图形的位图对象。其代码为<div class="blockcode"><div id="code_3"><ol><li> <br/></li> <li>/// <summary> <br/></li> <li>/// 创建一个包含对象图形位图对象 <br/></li> <li>/// </summary> <br/></li> <li>/// <returns>创建的位图对象</returns> <br/></li> <li>public Bitmap CreateBitmap( ) <br/></li> <li>{ <br/></li> <li> Bitmap bmp = new Bitmap( intWidth + 1 , intHeight + 1 ) ; <br/></li> <li> using( Graphics g = Graphics.FromImage( bmp )) <br/></li> <li> { <br/></li> <li> g.Clear( Color.White ); <br/></li> <li> g.TranslateTransform( intLeft , intTop ); <br/></li> <li> g.SmoothingMode = SmoothingMode.HighQuality ; <br/></li> <li> Draw( g , this.Bounds ); <br/></li> <li> } <br/></li> <li> return bmp ; <br/></li> <li>} <br/></li> </ol></div><em onclick="copycode($('code_3'));">复制代码</em></div>这个函数里面首先根据对象大小生成一个位图对象,然后在这个位图对象上创建一个图形绘制对象,填充白色背景,并进行一下坐标转换,然后调用Draw函数绘制对象图形。然后函数就返回创建的位图对象了。<br/> 这个对象还定义了一个GetHtmlString的函数向主页面提供一个显示饼图的HTML代码。<div class="blockcode"><div id="code_4"><ol><li> <br/></li> <li>/// <summary> <br/></li> <li>/// 创建用于显示饼图图片和超链接的HTML代码字符串 <br/></li> <li>/// </summary> <br/></li> <li>/// <param name="imgsrc">图片地址</param> <br/></li> <li>/// <returns>创建的HTML字符串</returns> <br/></li> <li>/// <remarks> <br/></li> <li>/// 此处没有简单拼凑HTML字符串,而是利用XML和HTML的相似性 <br/></li> <li>/// 使用一个XmlTextWriter来生成HTML字符串。 <br/></li> <li>/// </remarks> <br/></li> <li>public string GetHtmlString( string imgsrc ) <br/></li> <li>{ <br/></li> <li> if( this.Count == 0 ) <br/></li> <li> return ""; <br/></li> <li> // 生成唯一的 map 元素名称 <br/></li> <li> string name = System.Guid.NewGuid().ToString("N"); <br/></li> <li> // 生成 XmlTextWriter 对象 <br/></li> <li> System.IO.StringWriter myStr = new System.IO.StringWriter(); <br/></li> <li> System.Xml.XmlTextWriter writer = new XmlTextWriter( myStr ); <br/></li> <li> writer.IndentChar = ' ' ; <br/></li> <li> writer.Indentation = 3 ; <br/></li> <li> writer.Formatting = System.Xml.Formatting.Indented ; <br/></li> <li> // 开始输出HTML <br/></li> <li> writer.WriteStartDocument(); <br/></li> <li> // 输出图片元素 <br/></li> <li> writer.WriteRaw("<img src='" + imgsrc + "' usemap='#" + name + "' border='0'/>"); <br/></li> <li> // 输出 map 元素 <br/></li> <li> writer.WriteStartElement("map"); <br/></li> <li> writer.WriteAttributeString("name" , name ); <br/></li> <li> foreach( PieShapeItem item in this ) <br/></li> <li> { <br/></li> <li> // 输出超链接区域 <br/></li> <li> Point[] ps = this.GetPoints( item ); <br/></li> <li> writer.WriteStartElement("area"); <br/></li> <li> writer.WriteAttributeString("shape" , "poly"); <br/></li> <li> writer.WriteStartAttribute("coords" , null ); <br/></li> <li> for( int iCount = 0 ; iCount < ps.Length ; iCount ++ ) <br/></li> <li> { <br/></li> <li> writer.WriteString( ps[ iCount ].X.ToString() ); <br/></li> <li> writer.WriteString("," ); <br/></li> <li> writer.WriteString( ps[ iCount ].Y.ToString() ); <br/></li> <li> writer.WriteString("," ); <br/></li> <li> } <br/></li> <li> writer.WriteEndAttribute(); <br/></li> <li> if( item.Link != null && item.Link.Length > 0 ) <br/></li> <li> { <br/></li> <li> writer.WriteAttributeString("href" , item.Link ); <br/></li> <li> } <br/></li> <li> writer.WriteAttributeString("title" , item.Text ); <br/></li> <li> writer.WriteEndElement(); <br/></li> <li> } <br/></li> <li> writer.WriteEndElement(); <br/></li> <li> writer.WriteEndDocument(); <br/></li> <li> writer.Close(); <br/></li> <li> string html = myStr.ToString(); <br/></li> <li> // 修正输出的HTML字符串 <br/></li> <li> int index = html.LastIndexOf("?>"); <br/></li> <li> if( index > 0 ) <br/></li> <li> { <br/></li> <li> html = html.Substring( index + 2 ); <br/></li> <li> } <br/></li> <li> return html ; <br/></li> <li>} <br/></li> <li>/// <summary> <br/></li> <li>/// 获得包围饼图区域的点坐标数组 <br/></li> <li>/// </summary> <br/></li> <li>/// <param name="item">饼图项目</param> <br/></li> <li>/// <returns>点坐标数组</returns> <br/></li> <li>private Point[] GetPoints( PieShapeItem item ) <br/></li> <li>{ <br/></li> <li> GraphicsPath path = CreatePath( item ); <br/></li> <li> path.Flatten(); <br/></li> <li> PointF[] ps = path.PathPoints ; <br/></li> <li> path.Dispose(); <br/></li> <li> Point[] ps2 = new Point[ ps.Length ] ; <br/></li> <li> for( int iCount = 0 ; iCount < ps.Length ; iCount ++ ) <br/></li> <li> { <br/></li> <li> ps2[ iCount ].X = ( int ) ( ps[ iCount ].X ); <br/></li> <li> ps2[ iCount ].Y = ( int ) ( ps[ iCount ].Y ); <br/></li> <li> } <br/></li> <li> return ps2 ; <br/></li> <li>} <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_4'));">复制代码</em></div>这个函数参数是图片URL地址,该地址有主页面的程序提供,首先我们使用GUID类型来创建一个唯一的名称,我们没有直接使用字符串拼凑来生成HTML字符串,而是使用XmlTextWriter来书写HTML字符串。<br/> 首先是输出img标签,然后输出一个map标签。<br/> 由于饼图项目可以带有超链接,这里就能生成map/area元素来在图片中定义热点,在HTML语法中,图片热点没有扇形区域种类,因此只好使用使用多边形来模拟扇形。也就是使用HTML标记”<area shape=poly coords=’多边形的顶点坐标’ />”来模拟扇形热点。<br/> 我们遍历所与的饼图项目,调用GetPoints函数获得模拟这个项目对应的扇形区域的点坐标,将这些点坐标数据填写到area元素的coord属性中,然后还填上title属性来显示提示文本,设置href属性来设置这个图片热点的超链接地址。<br/> HTML文档输出完毕后我们获得这个XML字符串,去掉前面的XML声明头,然后就把这个XML字符串当作HTML字符串返回给主程序了。<br/> GetPoints函数就是获得模拟扇形区域的点坐标,首先是根据饼图项目创建一个路径,调用路径的Flatten函数进行线段模拟运算,然后获得它的PathPoints属性,这个属性值是PointF类型的数组,需要转换为Point数组。<br/>主页面 pie.aspx <br/> 这个页面是演示程序的主页面,界面上放置一个名为lblResult的标签,察看它的C#代码也不复杂,主要是Page_Load方法,其代码为<div class="blockcode"><div id="code_5"><ol><li> <br/></li> <li>private void Page_Load(object sender, System.EventArgs e) <br/></li> <li>{ <br/></li> <li> // 连接数据库 <br/></li> <li> using( OleDbConnection conn = new OleDbConnection()) <br/></li> <li> { <br/></li> <li> conn.ConnectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" <br/></li> <li> + this.Server.MapPath("demomdb.mdb"); <br/></li> <li> conn.Open(); <br/></li> <li> // 查询数据库 <br/></li> <li> using( OleDbCommand cmd = conn.CreateCommand()) <br/></li> <li> { <br/></li> <li> cmd.CommandText = @" <br/></li> <li>select top 10 <br/></li> <li> customers.customerid , <br/></li> <li> customers.companyname as 客户名称, <br/></li> <li> ( select sum( round( orderdetails.unitprice <br/></li> <li> * orderdetails.quantity <br/></li> <li> * ( 1.0 - orderdetails.discount) , 2 ) ) <br/></li> <li> from orderdetails , orders <br/></li> <li> where orderdetails.orderid = orders.orderid <br/></li> <li> and orders.customerid = customers.customerid <br/></li> <li> ) as 订单总金额 <br/></li> <li>from customers"; <br/></li> <li> OleDbDataReader reader = cmd.ExecuteReader(); <br/></li> <li> // 创建饼图对象 <br/></li> <li> PieShape pie = new PieShape(); <br/></li> <li> pie.Width = 400 ; <br/></li> <li> pie.Height = 300 ; <br/></li> <li> System.IO.StringWriter writer = new System.IO.StringWriter(); <br/></li> <li> while( reader.Read()) <br/></li> <li> { <br/></li> <li> string id = Convert.ToString( reader.GetValue( 0 )); <br/></li> <li> double Value = Convert.ToDouble( reader.GetValue( 2 )); <br/></li> <li> string Text = "客户名称:" + Convert.ToString( reader.GetValue( 1 )) <br/></li> <li> + "\r\n订单金额:" + Convert.ToString( reader.GetValue( 2 )) <br/></li> <li> + "\r\n点击察看该客户订单的详细情况" ; <br/></li> <li> string Link = "pie_orders.aspx?customerid=" + id ; <br/></li> <li> pie.Add( Value , Text , Link ); <br/></li> <li> }//while <br/></li> <li> reader.Close(); <br/></li> <li> pie.RefreshState(); <br/></li> <li> this.Session["pie_customers"] = pie ; <br/></li> <li> this.lblResult.Text = pie.GetHtmlString("pieimage.aspx?name=pie_customers"); <br/></li> <li> this.DataGrid1.DataSource = pie ; <br/></li> <li> this.DataGrid1.DataBind(); <br/></li> <li> } <br/></li> <li> } <br/></li> <li>} <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_5'));">复制代码</em></div>这个函数中首先是连接数据库,并执行一个SQL查询,查询结果是10个客户编号,名称及其名下所有的订单总金额。<br/> 创建一个饼图文档对象,然后遍历查询结果,对每一条记录都调用饼图文档对象的Add方法向饼图文档中添加一个饼图项目。<br/> 调用饼图文档对象的RefreshState方法刷新饼图的内部状态,然后将饼图对象保存到Session中拱将来的图片服务页面使用。然后设置lblResult标签的文本为饼图对象生成的HTML字符串。<br/> 这里还有一个DataGrid只是用来简单的显示饼图文档的内容。<br/>图片服务页面 pieimage.aspx <br/> 本页面用来生成饼图图形的图像文档,该页面没有任何HTML代码,其C#代码也很简单,只有一个Page_Load函数,其代码为<div class="blockcode"><div id="code_6"><ol><li> <br/></li> <li>private void Page_Load(object sender, System.EventArgs e) <br/></li> <li>{ <br/></li> <li> // 获得参数 <br/></li> <li> string name = this.Request.QueryString["name"] ; <br/></li> <li> if( name == null ) <br/></li> <li> { <br/></li> <li> return ; <br/></li> <li> } <br/></li> <li> // 获得饼图对象 <br/></li> <li> PieShape pie = this.Session[ name ] as PieShape ; <br/></li> <li> if( pie == null ) <br/></li> <li> { <br/></li> <li> return ; <br/></li> <li> } <br/></li> <li> using( Bitmap bmp = pie.CreateBitmap()) <br/></li> <li> { <br/></li> <li> this.Response.ContentType = "image/png"; <br/></li> <li> System.IO.MemoryStream ms = new System.IO.MemoryStream(); <br/></li> <li> bmp.Save( ms , System.Drawing.Imaging.ImageFormat.Png ); <br/></li> <li> ms.WriteTo( this.Response.OutputStream ); <br/></li> <li> ms.Close(); <br/></li> <li> } <br/></li> <li>} <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_6'));">复制代码</em></div>该页面试图从session中加载页面参数指定的名称的饼图文档对象,并利用该文档对象的CreateBmp函数获得一个位图对象,然后输出PNG格式的图像数据。<br/> 这个图片服务页面要正常工作,需要事先将饼图文档对象保存到Session中,然后使用正确的参数来调用这个页面。<br/> 主页面 pie_customers.aspx中,已经将生成的文档对象使用名称pie_customers保存到Session中,同时它生成的HTML代码中使用的图片地址就是 pieimage.aspx?name=pie_custoemrs ,主页面为了图片服务页面正常工作已经做好了充分的准备。由于主页面和图片服务页面密切配合工作,客户端浏览器中才能完整的显示饼图图形。<br/> 在这里使用了位图对象的Save函数来输出图片文档的二进制数据。这里没有直接输出到页面的输出流中,因为那样做是会报错的,这里创建了一个临时的内存流对象,将图片数据输出到这个内存流中,然后将这个内存流中的数据输出到页面输出流中。<br/>二级主页面 pie_orders.aspx <br/> 在主页面pie_custoemrs.aspx中新增饼图元素时还设置了饼图项目的超链接“pie_orders.aspx?customerid=客户编号”,这样用户点击饼图图片中的热点时就能跳转到二级主页面来显示指定客户的订单详细信息。<br/> 二级主页面和主页面有点类似,都用一个饼图来显示数据。其页面结构和处理过程也差不多,它的Page_Load方法代码为<div class="blockcode"><div id="code_7"><ol><li> <br/></li> <li>private void Page_Load(object sender, System.EventArgs e) <br/></li> <li>{ <br/></li> <li> string customerid = this.Request.QueryString["customerid"] ; <br/></li> <li> if( customerid == null || customerid.Length == 0 ) <br/></li> <li> return ; <br/></li> <li> // 连接数据库 <br/></li> <li> using( OleDbConnection conn = new OleDbConnection()) <br/></li> <li> { <br/></li> <li> conn.ConnectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" <br/></li> <li> + this.Server.MapPath("demomdb.mdb"); <br/></li> <li> conn.Open(); <br/></li> <li> // 查询数据库 <br/></li> <li> using( OleDbCommand cmd = conn.CreateCommand()) <br/></li> <li> { <br/></li> <li> cmd.CommandText = @" <br/></li> <li>SELECT OrderDate AS 订购时间, <br/></li> <li> shipname AS 运输人, <br/></li> <li> shipaddress AS 地点, <br/></li> <li> ( select <br/></li> <li> sum( round( unitprice * quantity * ( 1 - discount) , 3 ) ) <br/></li> <li> from orderdetails <br/></li> <li> where orderdetails.orderid = orders.orderid <br/></li> <li> ) AS 总金额 <br/></li> <li>FROM orders <br/></li> <li>WHERE customerid ='" + customerid + "'" ; <br/></li> <li> OleDbDataReader reader = cmd.ExecuteReader(); <br/></li> <li> // 创建饼图对象 <br/></li> <li> PieShape pie = new PieShape(); <br/></li> <li> pie.Width = 400 ; <br/></li> <li> pie.Height = 300 ; <br/></li> <li> System.IO.StringWriter writer = new System.IO.StringWriter(); <br/></li> <li> while( reader.Read()) <br/></li> <li> { <br/></li> <li> double Value = Convert.ToDouble( reader.GetValue( 3 )); <br/></li> <li> string Text = "时间:" + reader.GetValue( 0 ) <br/></li> <li> + "\r\n人员:" + reader.GetValue( 1 ) <br/></li> <li> + "\r\n地点:" + reader.GetValue( 2 ) <br/></li> <li> + "\r\n金额:" + reader.GetValue( 3 ); <br/></li> <li> string Link = "#" ; <br/></li> <li> pie.Add( Value , Text , Link ); <br/></li> <li> }//while <br/></li> <li> reader.Close(); <br/></li> <li> // 刷新饼图状态 <br/></li> <li> pie.RefreshState(); <br/></li> <li> this.Session["customerid"] = pie ; <br/></li> <li> this.lblResult.Text = pie.GetHtmlString("pieimage.aspx?name=customerid"); <br/></li> <li> this.DataGrid1.DataSource = pie ; <br/></li> <li> this.DataGrid1.DataBind(); <br/></li> <li> }//using <br/></li> <li> }//using <br/></li> <li>} <br/></li> <li> <br/></li> </ol></div><em onclick="copycode($('code_7'));">复制代码</em></div>本页面中,首先从页面参数中获得客户编号,然后连接数据库,进行SQL查询,获得指定客户编号的所有订单记录。然后将查询所得数据填充到一个饼图文档对象,然后将该文档对象使用名称customerid保存到Session中,这样未来运行的图片服务页面就从Session中获得名称为cusotmerid的饼图文档对象来显示饼图图片了。<br/>小结 <br/> 在本次课程中,我们研究在在ASP.NET中使用图形编程来显示一个带超链接的饼图图形,可以看出在ASP.NET中由于WEB程序的机构特点,其图形编程比在桌面开发中要复杂,涉及了很多知识,其实是HTML和图形编程的混合。
支持:) :) :)