原文:HTML5 Programming for ASP.NET Developers
协议:CC BY-NC-SA 4.0
四、使用画布画画
Web 流行的原因之一是提供给最终用户的图形用户界面。从最终用户的角度来看,图像、动画、字体和其他交互式效果使网站更具吸引力。然而,网站开发人员在开发 HTML5 之前的 web 应用时可能会遇到的一个限制是使用客户端功能在浏览器中绘制图形。ASP.NET 开发人员一直使用System.Drawing
名称空间在服务器上动态生成图形,然后将它们发送到客户端,但没有对在浏览器窗口中绘制图形的原生支持。
HTML5 通过提供画布在客户端图形渲染方面做得很好。顾名思义,画布是网页的一个矩形区域,您可以在画布 API 和 JavaScript/jQuery 的帮助下在其中执行绘制操作。在本章中,您将详细了解 HTML5 画布。具体来说,您需要查看以下内容:
- Draw lines, curves, paths, shapes and text on the canvas.
- Apply special effects such as shadow, gradient, pattern filling and transparency to drawing objects.
- Save the canvas state on the server for later use.
本章最后构建了一个包含您所学内容的示例应用。
<画布>元素
如前所述,HTML5 画布是网页上的一个矩形空间,您可以在其中执行绘制操作。HTML5 画布由<canvas>
标签表示。截至本文撰写之时,所有主流浏览器(如 Firefox、Chrome、Opera 和 Safari)的最新版本都支持<canvas>
元素,尽管支持因所提供的画布特性而异。
您可以使用清单 4-1 中的标记在网页上放置画布。
***清单 4-1。*基本<canvas>
元素
<head runat="server"> <script type="text/javascript" src="Script/jquery-1.7.2.min.js"></script> <script type="text/javascript" src="Script/modernizr-2.5.3.js"></script> <script type="text/javascript"> $(document).ready(function () {
` if (!Modernizr.canvas) {
alert(“This browser doesn’t support HTML5 Canvas!”);
}
});
清单 4-1 中的标记使用一个<canvas>
元素声明了一个 HTML5 画布。尽管它们是可选的,画布的宽度和高度也被设置来标记画布的尺寸。如果您没有为画布指定尺寸,浏览器会创建一个具有默认宽度和高度的画布。例如,如果没有指定尺寸,Firefox 和 Google 会创建一个宽 300 像素、高 150 像素的画布。<head>
部分还包含使用 Modernizr 检查画布支持的 jQuery 代码。<style>
部分为画布添加了一个边框,这样就可以在网页上看到它的边界。图 4-1 显示了画布的外观。
***图 4-1。*在浏览器中渲染的基本画布
在画布上画画
为了在画布上绘制任何东西,您需要获得它的绘制上下文。一个绘图上下文是一个为在画布上绘制图形提供方法和属性的对象。对上下文属性的任何更改都将应用于后续的绘图操作。此外,您可以使用不同的上下文对象执行不同的绘图操作。
要在代码中获得对绘图上下文的引用,可以使用<canvas>
DOM 元素的getContext()
方法。清单 4-2 展示了如何操作。
***清单 4-2。*获取绘图上下文
$(document).ready(function () { var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); });
正如您在上一章中所做的,这里您使用 jQuery get(0)
方法来获取对 id 为MyCanvas
的<canvas>
元素的引用。正如在第二章中提到的,jQuery 选择器返回匹配元素的集合。您可以使用get()
方法,通过指定从零开始的索引,从集合中检索元素。在这种情况下,使用 ID 选择了<canvas>
元素。自然,集合只有一个元素,因此index
被指定为0
。然后调用 canvas 对象的getContext()
方法来检索它的绘图上下文。getContext()
方法接受一个指示上下文类型的参数。值2d
表示二维绘图内容(当前唯一可用的模式)。
一旦获得了绘图上下文对象,就可以在画布上绘图了。在画布上执行绘制操作时,您需要指定要绘制图形的坐标,因此您应该知道画布坐标系。指定画布坐标的单位是像素。画布的左上角有坐标(0,0)。图 4-2 举例说明画布坐标。
图 4-2。 HTML5 画布坐标系
该图显示了一个宽 300 像素、高 200 像素的画布。画布的原点在(0,0)。还示出了(150,100)处的另一点。
绘制线条
在画布上画一条线可能是你能执行的最简单的操作。要绘制一条线,您需要执行以下操作:
- Move the current drawing position to the desired starting point.
- Specify the coordinates of the end point of the straight line.
- Draw a line.
清单 4-3 显示了所有三种操作。
***清单 4-3。*划清界限
context.moveTo(10, 100); context.lineTo(190, 100); context.stroke();
该代码假设context
变量包含一个对绘图上下文的引用。要绘制起点坐标为(10,100)和终点坐标为(190,100)的直线,您可以使用绘图上下文:
moveTo()
、lineTo()
和stroke()
的三种方法。
moveTo()
方法将当前绘图坐标移动到指定的 x 和 y 点(本例中为 10 和 100)。lineTo()
方法指定线条的结束坐标(在本例中为 190,100)。在您调用了lineTo()
方法之后,这条线并没有立即画出来。您通知绘图上下文,绘图操作将使用stroke()
方法执行。来自清单 4-3 的代码产生了图 4-3 所示的行。
***图 4-3。*划清界限
在这种情况下绘制的线采用默认宽度。但是,您可以更改正在绘制的线条的宽度,如下一节所述。
改变线条宽度和宽度
在前面的示例中,您在绘制直线时使用了线宽和线帽的默认值。您可以使用lineWidth
和lineCap
属性更改这些默认值。
属性以像素为单位指定线条的宽度。设置lineWidth
后绘制的所有线条在渲染时都采用新指定的宽度。
线条 cap 指的是如何绘制线条的末端。lineCap
属性有三个可能的值:butt
、round
和square
。默认是butt
。图 4-4 显示了三种线尾的区别。
图 4-4。butt
、round
、square
和的区别
如图 4-4 中的所示,butt
的一个lineCap
值正好在lineTo()
方法指定的终点坐标处结束。另一方面,round
和square
的lineCap
值以等于线宽度的量扩展线,因为等于线宽度一半的附加像素被用于帽。例如,假设一条线的宽度为 10 个像素,起点(10,100)和终点(190,100)。包括起点和终点的线的长度是 180 像素。如果您指定butt
的lineCap
模式,则产生的线条长度正好为 180 像素。然而,使用round
或square
的lineCap
模式,线的长度为 190 个像素(180 个像素+在一条线的开始处的额外 5 个像素+在结束处的额外 5 个像素)。
清单 4-4 显示了如何使用lineWidth
和lineCap
属性,图 4-5 显示了结果行。
***图 4-5。*不同的lineCap
设置在动作中
***清单 4-4。*设置lineWidth
和lineCap
属性
`context.lineWidth = 10;
context.beginPath();
context.moveTo(20, 100);
context.lineTo(180, 100);
context.lineCap = “butt”;
context.stroke();
context.beginPath();
context.moveTo(20, 120);
context.lineTo(180, 120);
context.lineCap = “round”;
context.stroke();
context.beginPath();
context.moveTo(20, 140);
context.lineTo(180, 140);
context.lineCap = “square”;
context.stroke();`
在这个清单中,使用lineWidth
属性将要绘制的线条宽度设置为 10 像素。然后,代码通过将lineCap
分别设置为butt
、round
和square
来绘制三条线。
注意beginPath()
方法的使用,它开始了一个新的绘图路径。(路径将在后面的章节中介绍。)在这里,可以说路径是一系列的绘制操作。如果你不调用beginPath()
,所有的线都用你最后指定的lineCap
重新绘制(在这个例子中是square
)。
注意图 4-5 中的最后两行是如何将额外的像素添加到总长度中的,而第一行是根据起点和终点坐标精确绘制的。
绘制曲线
您可以在画布上绘制三种类型的曲线:
弧线、二次曲线和贝塞尔曲线。这三种类型的曲线使用arc()
、arcTo()
、quadraticCurveTo()
和bezierCurveTo()
方法绘制。为了画圆和圆角矩形,画圆弧是必要的。你可以用arc()
和arcTo()
完成所有你需要的绘图,但是如果你需要更复杂的东西,你可以研究二次曲线和贝塞尔曲线。以下章节讨论arc()
和arcTo()
。
弧度的快速介绍
使用圆弧时,您经常会遇到以弧度指定的角度。弧度是角度测量的标准单位,描述了圆弧的长度与其半径之比。一个弧度被定义为当一个圆的弧的长度等于该圆的半径时对着的角。因此,弧度等于(弧的长度/弧的半径)。
圆的周长由公式 2πr 给出,其中 r 是圆的半径。一整圈意味着 360°或 2πr/r—即 2π和 1 弧度=(180°/π)。要将弧度转换为度数,需要将弧度值乘以 180/π。同样,要将度数转换为弧度,需要将度数乘以π/180。
注本讨论仅限于使用arc()
和arcTo()
方法所需的最低限度的理解。解释绘制这些曲线所涉及的数学处理超出了本书的范围。
使用 Arc()方法绘制圆弧
要在画布上画一条弧线,可以使用绘图上下文的arc()
方法。arc()
的一般语法如下:
context.arc(x, y, radius, start_angle, end_angle, direction)
arc()
方法的前两个参数表示圆弧中心的坐标。radius
参数表示圆弧的半径。起始角度和结束角度分别是弧的起点和终点的弧度角度。最后,direction
参数表示应该顺时针还是逆时针画圆弧。为了更好地理解这些参数,请看图 4-6。
图 4-6。arc()
方法参数的图示
清单 4-5 展示了一段使用arc()
方法绘制弧线的代码片段。
***清单 4-5。*使用arc()
方法
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var x = canvas.width / 2; var y = canvas.height / 2; var radius = 100; var start_angle = 0.5 * Math.PI; var end_angle = 1.75 * Math.PI; context.arc(x, y, 75, start_angle, end_angle, false); context.lineWidth = 20; context.stroke();
这段代码计算画布的中心,并使用这些坐标作为弧线的 x 和 y 坐标。注意开始和结束角度是如何使用Math.PI
常量以弧度计算的。direction
参数设置为false
,表示应该顺时针方向画圆弧。图 4-7 显示了清单 4-5 中的代码如何在浏览器中呈现。
图 4-7。arc()
方法在起作用
还有一种不同的方法来画圆弧:使用arcTo()
方法。该方法采用以下形式:
context.arcTo(x1, y1, x2, y2, radius)
在这个方法签名中,x1
、y1
、x2
和y2
是控制点,radius
是圆弧的半径。确定控制点有点复杂;图 4-8 说明了。
***图 4-8。*在arcTo()
方法中使用的坐标
注意不要太详细,控制点是附加在曲线上的特殊点,用来改变曲线的形状和角度。拉伸控制点会改变曲线的形状,而旋转控制点会改变曲线的角度。
在绘制圆角矩形时,arcTo()
方法很方便。清单 4-6 展示了如何操作。
***清单 4-6。*使用arcTo()
方法
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var x = 25; var y = 50; var width = 150; var height = 100; var radius = 20; context.lineWidth = 10; // top and top right corner context.moveTo(x + radius, y); context.arcTo(x + width, y, x + width, y + radius, radius); // right side and bottom right corner context.arcTo(x + width, y + height, x + width - radius, y + height, radius); // bottom and bottom left corner context.arcTo(x, y + height, x, y + height - radius, radius); // left and top left corner context.arcTo(x, y, x + radius, y, radius); context.stroke();
这段代码调用了arcTo()
方法四次。第一个调用绘制矩形的上边缘和右上角。第二个调用绘制右边和右下角。第三个调用绘制底边和左下角。最后,第四个调用绘制左边缘和左上角。图 4-9 显示了圆角矩形在运行时的样子。
**图 4-9。**使用arcTo()
方法绘制圆角矩形
本节讨论的arc()
和arcTo()
方法对于绘制圆形、扇形和圆角矩形非常有用。
绘制路径
在前面的部分中,您学习了绘制直线和曲线。您可以结合直线和曲线来绘制路径或形状。简单地说,路径是一系列产生形状的绘图操作。路径可以是开放的,也可以是封闭的。封闭的路径也可以用某种颜色或图案填充。
要绘制路径,首先调用beginPath()
方法,然后像以前一样绘制直线或曲线。要标记路径的结尾,您可以调用stroke()
(绘制形状的轮廓)或fill()
(绘制填充的形状)。
清单 4-7 使用moveTo()
和arc()
方法绘制一个笑脸。
***清单 4-7。*画一个笑脸
`var canvas = $(“#MyCanvas”).get(0);
var context = canvas.getContext(“2d”);
context.beginPath();
//face
context.arc(100, 100, 80, 0, Math.PI * 2, false);
//smile
context.moveTo(160, 100);
context.arc(100, 100, 60, 0, Math.PI, false);
//left eye
context.moveTo(75, 70);
context.arc(65, 70, 10, 0, Math.PI * 2, true);
//right eye
context.moveTo(135, 70);
context.arc(125, 70, 10, 0, Math.PI * 2, true);
context.stroke();
context.lineWidth = 5;
context.stroke();`
总之,有四个对arc()
方法的调用:它们分别绘制脸部、微笑、左眼和右眼的轮廓。为了实际绘制路径,代码调用了stroke()
方法— stroke()
绘制路径的轮廓。图 4-10 显示了浏览器中的笑脸。
您还可以绘制由某种颜色填充的闭合路径。例如,清单 4-8 绘制了一个填充蓝色的三角形。
***清单 4-8。*绘制填充形状
context.beginPath(); context.moveTo(50,20); context.lineTo(50,100); context.lineTo(150, 100); context.closePath(); context.lineWidth = 10; context.strokeStyle = 'red'; context.fillStyle = 'blue'; context.stroke(); context.fill();
***图 4-10。*画一个笑脸
注意使用了两个至今没有使用的方法:
closePath()
和fill()
。closePath()
方法通过连接第一个绘图操作的起点和最后一个绘图操作的终点来闭合路径。因此,要画一个三角形,你只需要用lineTo()
画两条线;调用closePath()
绘制三角形的第三条边。到目前为止,你一直使用stroke()
方法来绘制一个形状的轮廓。方法用一种颜色填充形状。当然,你可以用stroke()
也可以用fill()
搭配单一造型。strokeStyle
和fillStyle
属性可用于指定执行轮廓或填充操作时应使用的颜色。图 4-11 显示了运行清单 4-8 中的代码后,填充的三角形是如何呈现的。
***图 4-11。*画一个实心三角形
***图 4-12。*画在画布上的矩形
就像三角形一样,矩形也可以使用前面讨论的路径绘制技术来绘制。然而,画一个矩形是如此普遍需要的操作,以至于有现成的方法——strokeRect()
和fillRect()
——可以简化你的工作。这些方法接受矩形左上角的坐标及其宽度和高度。strokeRect()
方法绘制矩形的轮廓,而fillRect()
绘制填充的矩形。清单 4-9 展示了如何使用这些方法,图 4-12 展示了结果矩形。
***清单 4-9。*画一个长方形
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); context.lineWidth = 5; **context.fillRect(10,10,180,50);** **context.strokeRect(10,80,180,50);**
注意还有一个方法——rect()
——可以用来画矩形。但是,如果使用rect()
,还需要调用stroke()
或fill()
来实际绘制矩形。strokeRect()
和fillRect()
方法同时执行这两个步骤。
绘图文本
到目前为止,您已经学会了在画布上绘制线条、曲线和形状。通常,您需要将文本作为绘图本身的一部分,并且迟早您会想要在画布上绘制文本。如您所料,绘图上下文为使用strokeText()
和fillText()
方法绘制文本提供了丰富的支持。此外,您可以指定用于绘制文本的字体、字体大小、对齐方式和基线。
清单 4-10 显示了如何在这些属性和方法的帮助下绘制文本。
***清单 4-10。*在画布上绘制文本
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var x = canvas.width / 2; var y = canvas.height / 2; context.font = "30px Arial"; context.textBaseline = "middle"; context.textAlign = "center"; context.lineWidth = 1; context.strokeStyle = "red"; context.fillStyle = "blue"; context.strokeText("Hello Canvas!",x,y-50); context.fillText("Hello Canvas!",x,y+50);
这段代码使用font
属性设置要绘制的文本的字体。请注意,为了正确绘制文本,字体大小和属性(如粗体和斜体)必须放在字体系列之前。
textBaseLine
属性控制文本的基线。textBaseLine
属性的一些常见值是top
、bottom
和middle
。这些值影响文本相对于 y 坐标的垂直位置。例如,将textBaseLine
属性设置为middle
意味着文本的垂直中点对应于 y 坐标。属性控制画布上的文本对齐。textAlign
的常用值有left
、right
和center
。textAlign
属性控制文本相对于文本的 x 位置的对齐。例如,如果 x 坐标设置为 100px,而textAlign
设置为center
,那么文本的中心将位于 100px。
您可以使用strokeStyle
属性来指定文本轮廓颜色。方法用指定的strokeStyle
在画布上绘制文本。
属性控制文本如何填充,方法用指定的 ?? 绘制文本。
图 4-13 显示了来自清单 4-10 的代码如何呈现指定的文本。
图 4-13*。*行动中的strokeText()
和fillText()
方法
注意没有内置的方式将一长行文本换行为多行文本。您必须根据自己的需要编写自己的代码来分割文本。
绘制图像
在 HTML5 画布上绘图不限于直线、曲线、形状和文本。您也可以在画布上绘制现有的图像文件。当您希望整合比简单形状更复杂的图像时,这尤其有用。例如,考虑一下,如果您想在画布上显示公司徽标。从头开始重新创建徽标可能不是最佳选择。此外,徽标可能太复杂,无法使用可用的画布 API 来绘制。加载现有的徽标图像并将其呈现在画布上要容易得多。幸运的是,绘图上下文提供了drawImage()
方法,它允许您在画布上绘制图像。
清单 4-11 显示了使用drawImage()
的最简单形式。
***清单 4-11。*使用最简单的drawImage()
方法
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var img = new Image(); $(img).load(function () { context.drawImage(img, 10, 20); }); img.src = "img/html5.png";
这段代码使用 JavaScript 代码动态创建一个图像。注意,jQuery 代码还为 image 对象的load
事件连接了一个事件处理程序。当图像完全加载时,会引发load
事件。除非图像被完全加载,否则不能在画布上绘制。因此,您调用了load
事件处理程序中的drawImage()
方法。drawImage()
接受要绘制的图像对象以及要绘制图像的 x 和 y 坐标。然后,代码将图像的src
属性设置为现有的图像文件(img/html5.png
)。如果您运行清单 4-11 中的代码,您会看到画布上绘制的图像,如图 4-14 所示。
在画布上绘制图像时,有时您可能需要调整图像的大小。例如,原始图像尺寸可能大于画布尺寸,您可能希望根据画布尺寸来调整图像。为了实现这一点,您可以使用一个drawImage()
方法的变体,它接受图像必须调整到的宽度和高度。清单 4-12 展示了这是如何做到的。
***清单 4-12。*绘制特定宽度和高度的图像
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var img = new Image(); $(img).load(function () { context.drawImage(img,0,0, canvas.width,canvas.height); }); img.src = "img/html5.png";
如您所见,除了 x 和 y 坐标,drawImage()
方法还指定了图像的宽度和高度。在清单 4-12 中,图像的宽度和高度被设置为等于画布的宽度和高度。当然,如果没有保持宽度和高度的比例,调整后的图像可能会变形。
图 4-14。 drawImage()
法在行动
在画布上绘制图像的另一种可能性是只绘制源图像的一部分,而不是整个图像。要完成这个图像裁剪,您可以使用另一种drawImage()
方法,它获取图像切片的坐标和尺寸以及前面讨论过的其他参数。清单 4-13 说明了如何使用这个变体。
***清单 4-13。*只画图像的一部分
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var img = new Image(); $(img).load(function () { context.drawImage(img, 0,0,200,40,0, 0, canvas.width/2 , canvas.height/2); }); img.src = "img/html5.png";
drawImage()
方法现在比以前的签名多接受四个参数。image
参数后的两个参数表示源图像的 x 和 y 坐标(0,0 ),裁剪应从该坐标开始。接下来的两个参数表示需要在画布上绘制的来自源图像的区域的宽度(200px)和高度(40px)。最后四个参数的意义与上一个示例中的相同。图 4-15 显示了如何在画布上绘制部分图像。
***图 4-15。*使用drawImage()
方法进行图像裁剪
正如你所看到的,HTML5 的 logo 已经被裁剪了,只有文本 HTML 被绘制在画布上。就像前面 drawImage()方法的变体一样,如果没有保持宽度和高度的比例,结果图像也会失真。
添加特效
到目前为止,您已经学习的 canvas 方法允许您呈现普通图形。但是,您也可以为图形添加奇特且引人注目的效果,如阴影、透明度、渐变填充和图案填充。在本节中,您将学习如何操作。
要添加特殊效果,您需要设置绘图上下文的某些属性,然后调用stroke()
或fill()
。让我们通过一些例子来看看这是如何做到的。
阴影
在画布上绘制图形时使用的一种常见效果是阴影。您可以使用绘图上下文对象的四个核心属性:
shadowColor
、shadowBlur
、shadowOffsetX
和shadowOffsetY
为线条、曲线、形状和文本添加阴影。
属性表示阴影的颜色。shadowBlur
属性是一个数字属性,用于配置阴影的模糊度。shadowBlur
值越低,阴影越清晰。例如,将shadowBlur
设置为10
会比5
的值给阴影增加更多的模糊度。shadowOffsetX
和shadowOffsetY
属性允许你控制阴影的位置。这些 x 和 y 坐标相对于显示阴影的目标图形对象。例如,如果您将shadowOffsetX
和shadowOffsetY
属性设置为 5 像素,那么阴影将从目标图形向右绘制 5 像素,向下绘制 5 像素。您还可以提供负数形式的shadowOffsetX
和shadowOffsetY
属性,以使阴影向相反的方向移动(向左和向上)。
清单 4-14 使用讨论过的阴影属性给矩形和文本添加阴影。
***清单 4-14。*使用阴影属性配置阴影
`var canvas = $(“#MyCanvas”).get(0);
var context = canvas.getContext(“2d”);
context.shadowColor = “#808080”;
context.shadowBlur = 5;
context.shadowOffsetX = 10;
context.shadowOffsetY = 10;
context.fillRect(20, 20, 150, 80);
context.shadowColor = “red”;
context.shadowBlur = 15;
context.shadowOffsetX = -5;
context.shadowOffsetY = 5;
context.fillStyle = “blue”;
context.textAlign = “center”;
context.font = “bold 30px Arial”;
context.fillText(“Hello Canvas!”,100,150);`
这段代码使用#808080 的阴影颜色和 10 像素的偏移量绘制了一个矩形。blur
值为5
。x 偏移值为-5
,shadowColor
为red
。图 4-16 显示了生成的矩形和文本的外观。
***图 4-16。*给矩形和文本添加阴影
画布上绘制的文本具有负的 x 阴影偏移值,因此阴影向左侧移动。y 阴影偏移量为正值,因此阴影向底部移动。
透明度
直到现在,所有在画布上绘制的图形对象都是完全不透明的。但是,您可以使用strokeStyle
和fillStyle
属性更改图形对象的透明度。这些属性允许您设置图形的颜色,除了 RGB 分量之外,它还指定透明度级别。这是使用rgba()
功能(而不是更常用的rgb()
功能)完成的。清单 4-15 展示了如何操作。
***清单 4-15。*设置绘图对象的透明度
`var canvas = $(“#MyCanvas”).get(0);
var context = canvas.getContext(“2d”);
context.fillStyle = “black”;
context.fillRect(20, 20, 150, 80);
context.fillStyle = “rgb(255, 0, 0)”;
context.fillRect(40, 40, 150, 80);
context.fillStyle = “black”;
context.fillRect(20, 150, 150, 80);
context.fillStyle = “rgba(255, 0, 0,0.5)”;
context.fillRect(40, 170, 150, 80);`
第一个代码块绘制了两个没有任何透明度的填充矩形。第二个代码块绘制了两个填充的矩形,第二个矩形的透明度为 50%。注意rgba()
函数是如何使用的。rgba()
的第四个参数 alpha 值——控制透明度。alpha 值可以在 0 到 1 之间,0 表示透明,1 表示不透明。图 4-17 显示了两组矩形是如何渲染的。
***图 4-17。*控制绘图对象的不透明度
注意在 CSS 中,一个 RGB 值可以用函数表示法格式rgb()
和rgba()
得到。rgb()
使用红色、蓝色和绿色成分形成颜色。rgba()
使用控制颜色透明度的附加组件(alpha)。
渐变填充
渐变指定用于填充形状的颜色范围。渐变效果在开始和结束位置之间连续应用颜色范围,从而产生平滑的颜色过渡。渐变可以有两种类型:
线性或径向。
线性渐变由两个点和每个点上的颜色定义。连接这两点的线条上的颜色根据颜色停止点而变化。
径向渐变由两个圆定义,每个圆都有一种颜色。渐变从开始的圆向结束的圆辐射,颜色是根据色阶计算的。
要用线性或径向渐变填充形状,首先需要创建相应的渐变对象,然后定义所需的色标。最后,使用设置为刚刚创建的渐变对象的fillStyle
属性填充一个形状。
清单 4-16 展示了如何用线性渐变填充一个矩形。
***清单 4-16。*绘制线性渐变
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var linearGradient = context.createLinearGradient(0, 100, 200, 100); linearGradient.addColorStop(0, "blue"); linearGradient.addColorStop(0.5, "green"); linearGradient.addColorStop(1, "red"); context.fillStyle = linearGradient; context.fillRect(0, 0, 200, 200);
这段代码使用绘图上下文的createLinearGradient()
方法创建一个线性渐变对象。createLinearGradient()
的前两个参数代表渐变起点的 x 和 y 坐标。类似地,最后两个参数表示终点的 x 和 y 坐标。渐变是从起点到终点绘制的。
代码添加了另外三个颜色停止点。颜色停止偏移值的范围从0
到1
(从渐变的开始到结束)。绘图上下文的fillStyle
属性被设置为线性渐变对象,然后fillRect()
被调用。图 4-18 显示了渐变填充矩形的结果。
***图 4-18。*线性渐变
使用渐变的起点和终点,您可以更改线性渐变的绘制方式。例如,如果你分别使用(50,10)和(200,100)的开始和结束坐标创建一个线性渐变对象,产生的渐变如图 4-19 所示。
***图 4-19。*改变渐变线的坐标
用径向渐变填充形状的过程与前面的示例类似,但有一些不同。首先,使用createRadialGradient()
方法创建一个径向渐变对象。第二,提供起点和终点圆的圆心坐标以及它们各自的半径,而不是起点和终点坐标。清单 4-17 展示了这是如何做到的。
***清单 4-17。*绘制径向渐变
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var radialGradient = context.createRadialGradient(100, 100, 5,100, 100,100); radialGradient.addColorStop(0, "blue"); radialGradient.addColorStop(0.5, "green"); radialGradient.addColorStop(1, "red"); context.fillStyle = radialGradient; context.fillRect(0, 0, 200, 200);
如您所见,createRadialGradient()
方法有六个参数。前三个代表起始圆的坐标(100,100)及其半径(5)。接下来的三个表示末端圆的坐标(100,100)及其半径(100)。图 4-20 显示了最终的矩形。
***图 4-20。*径向梯度
渐变从蓝色开始,然后变成绿色,然后由于0
、0.5
和1
的色阶值而变成红色。
图案填充
图案填充涉及用图像填充形状,通常在 x 轴、y 轴或两个轴上重复。为了使用图案填充,您使用绘图上下文的createPattern()
方法,然后将fillStyle
属性设置为新创建的图案。清单 4-18 展示了你如何创建一个模式。
***清单 4-18。*创建和填充图案
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); var img = new Image(); $(img).load(function () {
var pattern = context.createPattern(img, "repeat"); context.fillStyle = pattern; context.fillRect(0, 0, 200, 200); }); img.src = "img/pattern.png";
这段代码创建了一个Image
对象的新实例,并将其src
属性设置为用于图案填充的图像。Image
的load
事件处理程序使用绘图上下文的createPattern()
方法创建一个图案。createPattern()
的第一个参数是要使用的图像,第二个参数表示重复方向。重复方向的可能值为repeat-x
(水平重复)、repeat-y
(垂直重复)和repeat
(两个方向重复)。然后将fillStyle
属性设置为新创建的模式对象。最后,绘制一个填充矩形。图 4-21 显示了如何用样本图案填充矩形。
***图 4-21。*用图案填充矩形
保存画布状态
到目前为止,您一直在通过在画布上执行各种绘图操作来玩画布。很多时候,你需要在绘图操作之间改变画布的状态。例如,假设您有一个画布,其中的设置如lineWidth
和fillStyle
被设置为某些值。现在你希望在画布上画三个矩形。然而,lineWidth
和fillStyle
对他们来说是不同的。在这种情况下,您为第一个矩形设置上下文属性,然后绘制该矩形。这样做会导致原始值被覆盖。重复同样的过程来绘制另外两个矩形。现在,如果您希望将画布恢复到开始绘制三个矩形之前的原始状态,您必须再次设置这些属性,因为它们会被后续的绘制操作覆盖。保存画布状态允许您在以后的某个阶段恢复它。
术语表示可能有点误导。这里的画布状态是指strokeStyle
、fillStyle
、lineWidth
、lineCap
、shadowOffsetX
、shadowOffsetY
、shadowBlur
、shadowColor
属性等设置以及其他一些设置。您可能想要存储画布设置在给定时间点的快照,以便稍后在当前浏览器会话中恢复到这些设置。为了执行这些任务,绘图上下文对象提供了两个方法:
save()
和restore()
。顾名思义,save()
方法保存画布状态,而restore()
方法恢复先前保存的状态。
使用保存()和恢复()方法
使用save()
和restore()
相对简单。save()
方法将画布状态的快照推送到堆栈上,而restore()
方法弹出画布状态的已保存快照。重要的是要记住画布状态并不是指画布上的实际绘制。清单 4-19 展示了如何使用save()
和restore()
。
***清单 4-19。*使用save()
和restore()
方法
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); //default state context.lineWidth = 5; context.fillStyle = 'blue'; context.fillRect(10, 120, 150, 50); context.save(); //change state context.lineWidth = 10; context.fillStyle = 'red'; context.fillRect(20, 130, 150, 50); //restore state context.restore(); context.fillRect(30, 140, 150, 50);
如您所见,首先设置了画布设置,如lineWidth
和fillStyle
,并绘制了一个矩形。使用save()
方法保存这些设置。接下来,将画布设置更改为新值,并绘制一个矩形。最后,在绘制第三个矩形之前,使用restore()
恢复画布设置。图 4-22 显示保存和恢复画布状态的效果。
***图 4-22。*保存和恢复画布状态的效果
注意在图 4-22 中,上面三个矩形的绘制没有使用save()
或restore()
。第三个矩形采用为第二个矩形设置的画布设置。
然而,底部的矩形使用save()
和restore()
。代码画完第一个矩形后,使用save()
保存画布状态。在绘制第三个矩形之前,使用restore()
恢复画布状态。这就是为什么第三个矩形采用恢复的设置,而不是绘制第二个矩形时使用的设置。
保存画布绘图
在前面的部分中,您在激活的浏览器任务中执行了图形操作。如果关闭浏览器窗口会怎样?如果您希望保存绘图以备后用,该怎么办?这就是你需要保存画布的地方。您可能希望将画布绘图保存在存储介质中,如本地存储器(在第七章中讨论)、服务器的物理文件系统或驻留在服务器上的 SQL server 数据库,以便您可以在同一或不同的浏览器会话中检索它。
为了满足这个需求,画布(不是绘图上下文!)提供了toDataURL()
方法。然而,这种方法本身并不能在服务器上持久化数据。它返回画布绘图的 Base64 表示。您有责任使用客户端技术(如 jQuery $.ajax()
方法)将这些 Base64 编码的数据发送到服务器。
除了保存画布绘图的内置技术之外,您还可以实现模仿保存操作的自定义技术。例如,您可以记住用户在画布上执行的所有绘图步骤。您可以不保存画布绘图,而是将这些步骤保存在服务器上,稍后重放相同的步骤来再现画布绘图。您使用的保存机制取决于您正在构建的应用的类型。下面几节主要关注保存画布绘图的内置技术。
使用 toDataURL()方法
画布的toDataURL()
方法返回绘图的 Base64 编码表示。由您决定如何处理这些 Base64 数据。一些可能性包括如下:
- Display canvas drawing in HTML image object.
- Send the Base64 encoded string to the server and save it in the database.
- Send the Base64 encoded string to the server and save it as an image file on the server.
- Save Base64-encoded strings in web memory for later retrieval.
您对画布节省技术的选择取决于应用的类型。在下面的例子中,你学习了除最后一个以外的所有技术;网络存储是第七章的主题。
注 Base64 编码通常用于需要将二进制数据转换为文本格式以便通过网络存储和传输的场合。Base64 通常用于对电子邮件附件进行编码,并在 XML 文档中存储二进制数据。
将画布绘图保存在< img >对象中
有时,您可能希望允许用户将画布绘图保存为物理图像文件。在这种情况下,将画布绘图存储为图像会很有用。保存图像后,用户可以在图像上单击鼠标右键,然后将图形保存在本地文件系统中,就像浏览器中加载的任何其他图像一样。
如果您希望允许用户创建多个绘图而不显式保存它们,然后在画布上再次加载以前创建的绘图,也可以将画布保存为图像。在这种情况下,您可以将不同的图形保存为图像,然后使用前面讨论的drawImage()
方法重新加载图形。
在<img>
对象中保存画布很简单。您需要做的就是将 DOM <img>
元素的src
属性分配给由toDataURL()
方法返回的 Base64 编码的数据。清单 4-20 展示了如何做到这一点。
***清单 4-20。*将画布保存为<img>
对象
var canvas = $("#MyCanvas").get(0); var context = canvas.getContext("2d"); context.fillRect(20, 20, 160, 160); **var data = canvas.toDataURL();** **$("#imgCanvas").attr("src", data);**
如清单所示,在画布上绘制了一个矩形,然后调用toDataURL()
来检索画布绘图的 Base64 编码表示。通过设置元素的src
属性,将这个 Base64 数据加载到一个<img>
元素中。下面显示了一个 Base64 编码字符串示例的一部分:
data:image/png;base64,iVBORw0KGgoAAAANSUh...
如您所见,该字符串以图像的 MIME 类型开始。您也可以使用toDataURL()
的变体自行指定图像类型:
**var data = canvas.toDataURL("mage/png");**
toDataURL()
现在接受图像的 MIME 类型。典型值包括image/png
和image/jpg
。默认的图像 MIME 类型是image/png
。
在 SQL Server 中保存画布图形
将画布绘图保存在 SQL Server 数据库中需要将调用toDataURL()
获得的 Base64 编码数据发送到服务器。您可以使用隐藏的表单字段来完成数据传输,或者更好的方法是使用 jQuery $.ajax()
方法。清单 4-21 使用$.ajax()
向服务器发送 Base64 编码的数据。
***清单 4-21。*使用$.ajax()
向服务器发送 Base64 画布数据
`var canvas = $(“#MyCanvas”).get(0);
var context = canvas.getContext(“2d”);
context.fillRect(20, 20, 160, 160);
KaTeX parse error: Expected 'EOF', got '#' at position 3: ('#̲btnSave').click….ajax({
type: ‘POST’,
url: ‘/SaveInSQLServer.aspx/SaveToDb’,
data: ‘{ “data” : "’ + data + ‘" }’,
contentType: ‘application/json; charset=utf-8’,
dataType: ‘json’,
success: function (msg) {
alert(‘Image data saved to SQL Server database!’);
}
});
});`
如此处所示,按钮的click
事件处理程序触发保存操作。在click
事件处理程序中,toDataURL()
被调用,Base64 编码的数据存储在一个局部变量中。您只需要将图像数据发送到服务器;因此,使用 JavaScript 的replace()
函数删除了字符串的开头('data:image/png;base64,'
)。当然,如果您希望使用前面讨论的数据 URL 技术在图像元素中呈现图像,您不需要删除字符串的开头。
然后,$.ajax()
方法向名为SaveToDb()
的 web 方法发出一个POST
请求。Base64 编码的图像数据作为 JSON 对象传递给服务器。SaveToDb()
web 方法如清单 4-22 所示。
清单 4-22。 SaveToDb()
Web 方法
[WebMethod] public static void SaveToDb(string data) { ImageDbEntities db = new ImageDbEntities(); Image img = new Image(); img.ImageData = data; img.SaveDate = DateTime.Now; db.Images.AddObject(img); db.SaveChanges(); }
SaveToDb()
方法接收 Base64 编码的图像数据作为参数。在内部,它使用一个实体框架数据模型类(Image
)将数据保存在一个名为Images
的 SQL Server 表中。如果您运行这个示例并从App_Data
文件夹中检查ImageDb
数据库,您会发现每次单击 Save 按钮都会有一条记录被添加到Images
表中。
在服务器上将画布绘图保存为图像文件
有时,将画布绘图保存为服务器上的物理磁盘文件比将图像存储在数据库中更方便。例如,考虑这样一种情况,您希望将画布绘图作为电子邮件附件发送。在这种情况下,附加保存为物理图像文件的画布绘图更加方便,并简化了您的任务。
将画布绘图保存为服务器端图像类似于前面的技术。但是,这一次,您需要将 Base64 编码的数据转换回其二进制表示形式,然后将其保存为磁盘文件。将 Base64 编码的数据转换为其二进制形式是必要的,因为您将它保存在物理图像文件中,而不是 SQL Server 数据库中。jQuery 代码与前一个示例几乎相同,但是 web 方法需要一些修改。清单 4-23 显示了对名为SaveAsImageFile
的 web 方法的$.ajax()
调用。
清单 4-23。 $.ajax()
方法调用SaveAsImageFile
Web 方法
$('#btnSave').click(function () { var data = canvas.toDataURL(); data = data.replace('data:image/png;base64,', ''); alert(data); $.ajax({ type: 'POST', url: '/SaveAsServerSideImg.aspx/SaveAsImageFile', data: '{ "data" : "' + data + '" }', contentType: 'application/json; charset=utf-8', dataType: 'json', success: function (msg) { alert('Image saved on the server!'); } }); });
一旦在 web 方法中接收到 Base64 编码的数据,它就被转换成二进制形式并保存为物理文件。清单 4-24 展示了SaveAsImageFile
() web 方法。
***清单 4-24。*将画布绘图保存为物理图像文件
[WebMethod] public static void SaveAsImageFile(string data) { Guid id = Guid.NewGuid(); string path = HttpContext.Current.Server.MapPath("~/img/" + id.ToString() + ".png"); byte[] binaryData = Convert.FromBase64String(data); FileStream file = new FileStream(path, FileMode.Create); BinaryWriter bw = new BinaryWriter(file); bw.Write(binaryData); bw.Close(); }
这段代码使用 GUID 和Server.MapPath()
方法生成一个随机文件名。物理文件的扩展名为.png
。要将 Base64 编码的数据转换成二进制形式,可以使用Convert
类的FromBase64String()
方法。此方法接受 Base64 编码的数据,并返回一个字节数组。使用FileStream
和BinaryWriter
类将返回的字节数组写入物理文件。
图 4-23 显示了一个保存为图像文件的样本画布,正在 Windows 照片查看器中查看。
***图 4-23。*保存为物理图像文件后的画布绘制
正如您在照片查看器的标题中看到的,该文件以 GUID 作为文件名,以.png
作为文件扩展名保存。
使用画布绘制技术创建饼图
既然您已经熟悉了 canvas API,那么是时候开发一个示例应用了,它将您到目前为止所学的内容整合在一起。在本节中,您将开发一个 ASP.NET MVC 应用,它允许您使用 HTML5 画布创建饼图。
开发人员经常求助于第三方图表组件来满足他们的图表需求。然而,有时你会发现基于画布的图表很有用。元素是 HTML5 不可或缺的一部分,所以你不需要购买商业组件。许多第三方组件在服务器端编程方面非常丰富,但它们通常缺乏客户端编程支持。当要使用客户端代码对图表进行编程时,可以使用基于画布的图表。使用基于画布的图表还可以让您避免依赖第三方图表软件。当然,如果您的需求需要,您可以求助于第三方图表组件。
示例应用的用户界面分为两部分,如图 4-24 所示。图 4-24 的左侧显示了最终的饼状图在浏览器中的样子,右侧显示了可以输入饼状图数据的区域,例如扇区名称、数值和颜色。您还可以为图表指定标题以及渐变背景。点按“绘制图表”按钮会根据您输入的数据在画布上绘制一个饼图。单击“保存图表”按钮有两个作用,一是将饼图作为图像文件保存在服务器上,二是将饼图扇区信息保存在 SQL Server 数据库中。这样,您可以根据保存的数据重新生成饼图,并将物理图像文件用于其他目的,如作为电子邮件附件发送、嵌入文档或演示文稿。
***图 4-24。*使用 canvas API 绘制饼状图
SQL Server 数据库
该应用将扇区名称及其值等饼图数据保存在 SQL Server 数据库中(ChartDb
)。ChartDb
数据库由两个表:
ChartMaster
和ChartDetails
组成。实际的数据访问是通过实体框架进行的。ChartMaster
和ChartDetails
表的实体框架数据模型实际上位于 web 应用的Models
文件夹中,如图 4-25 所示。
图 4-25。ChartDb
数据库表的实体框架数据模型
如您所见,ChartMaster
表存储了饼图标题和物理图像文件 URL。ChartDetails
表存储扇区信息,如名称、值和颜色。虽然这个应用不基于存储的数据重新生成图表,但是您可以很容易地做到这一点,因为数据在这些表中很容易获得。
MVC 控制器
ASP.NET MVC 应用只有一个控制器:ChartController
。它包含将饼图保存为图像的代码。它还将饼图数据保存到ChartMaster
和ChartDetails
表中。完成这项工作的ChartController
类的动作方法如清单 4-25 所示。
***清单 4-25。*在服务器上保存图表图像
`[HttpPost]
public JsonResult SaveChart(string data, ChartMaster master, ChartDetail[] details)
{
Guid id = Guid.NewGuid();
string path = HttpContext.Server.MapPath(“~/img/” + id.ToString() + “.png”);
byte[] binaryData = Convert.FromBase64String(data);
FileStream file = new FileStream(path, FileMode.Create);
BinaryWriter bw = new BinaryWriter(file);
bw.Write(binaryData);
bw.Close();
ChartDbEntities db = new ChartDbEntities();
master.Id = id;
master.ImageUrl = “~/img/” + id.ToString() + “.png”;
db.ChartMasters.AddObject(master);
foreach (ChartDetail detail in details)
{
detail.ChartId = master.Id;
db.ChartDetails.AddObject(detail);
}
db.SaveChanges();
return Json(“Chart saved in the database!”);
}`
如您所见,SaveChart()
方法接受 Base64 格式的三个参数:
画布绘制数据、一个ChartMaster
对象和一个ChartDetail
对象数组。这三个参数是从客户端 jQuery 代码发送的。
SaveChart()
中的代码看起来应该很熟悉,因为前面的章节已经讨论了这些画布节省技术。请注意 GUID 如何实现双重目的:
,充当图表 ID 和物理文件名。SaveChart()
方法主要做两件事:它将饼图保存为服务器上的物理.png
文件,并将图表数据保存在ChartMaster
和ChartDetails
表中。在服务器上保存图表后,SaveChart()
以JsonResult
对象的形式返回成功消息。
MVC 视图
HTML5 画布标记以及调用控制器动作的 jQuery 代码驻留在一个视图中:Index.aspx
。索引视图的 HTML 标记如清单 4-26 所示。为了清楚起见,去掉了不需要的标记。
*清单 4-26。Index.aspx
*的 HTML 标记
`
Chart Title :…
Sector Name | Sector Value | Color | Action |
---|---|---|---|
Show Gradient ... Gradient Color : ... ... `
这个 HTML 标记很简单,创建了一个 600 像素宽、500 像素高的画布。“添加”、“绘制图表”和“保存图表”按钮的事件处理程序是在 jQuery 代码中连接的,接下来将对此进行讨论。
添加图表数据
添加饼图数据涉及捕获扇区信息,如扇区名称、扇区值和扇区颜色。一旦捕获,扇区信息被添加到 HTML 表中(见图 4-24 )。负责向 HTML 表格添加扇区信息的 jQuery 代码如清单 4-27 所示。
***清单 4-27。*向 HTML 表格添加图表数据
$("#btnAdd").click(function () { var row = "<tr>"; row += "<td>" + $("#txtName").val() + "</td>"; row += "<td>" + $("#txtValue").val() + "</td>";
row += "<td>" + $("#txtColor").val() + "</td>"; row += "<td> </td>"; row += "</tr>"; $("#tblChartData").append(row); $("#txtName").val(''); $("#txtValue").val(''); $("#txtColor").val(''); $("#txtName").focus(); });
如您所见,来自 Add 按钮的click
事件处理程序的 jQuery 代码使用val()
方法来检索文本框中输入的值。然后使用append()
方法动态添加一个新的表格行。
注意你可能会觉得输入扇区颜色有点乏味。本章的代码下载包括一个 JavaScript 函数,该函数在接收到焦点时自动用随机的十六进制颜色代码填充颜色文本框。为了节省空间,这里不讨论这个函数。
绘制饼状图
绘制图表按钮的click
事件处理程序负责在画布上绘制饼图。click
事件处理程序的完整 jQuery 代码在清单 4-28 中给出。
***清单 4-28。*绘制饼状图
`var sectorNames = Array();
var sectorValues = Array();
var sectorColors = Array();
KaTeX parse error: Expected 'EOF', got '#' at position 3: ("#̲btnDraw").click…(“#chkGradient”).is(“:checked”)) {
var linearGradient = context.createLinearGradient(0, 0, canvas.width, canvas.height);
linearGradient.addColorStop(0, $(“#txtGradient”).val());
linearGradient.addColorStop(1, ‘white’);
context.fillStyle = linearGradient;
context.fillRect(0, 0, canvas.width, canvas.height);
context.restore();
}
else {
context.clearRect(0, 0, canvas.width, canvas.height);
}
context.save();
$(“table tr:gt(1)”).each(function (i, v) {
sectorNames[i] = KaTeX parse error: Expected 'EOF', got '}' at position 39: …0)").text(); }̲) (“table tr:gt(1)”).each(function (i, v) {
sectorValues[i] = parseInt(KaTeX parse error: Expected 'EOF', got '}' at position 42: …).text());` ` }̲) (“table tr:gt(1)”).each(function (i, v) {
sectorColors[i] = $(this).children(“td:eq(2)”).text();
})
//draw title
context.textAlign = “center”;
context.font = “bold 30px Arial”;
context.shadowColor = “silver”;
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.fillStyle = “black”;
context.fillText($(“#txtTitle”).val(), 300,50 );
context.restore();
//draw sectors
var total = 0;
for (var i = 0; i < sectorValues.length; i++) {
total += sectorValues[i];
}
var angle = 0;
for (var i = 0; i < sectorValues.length; i++) {
context.fillStyle = sectorColors[i];
context.beginPath();
context.moveTo(170, 250);
context.arc(170, 250, 150, angle, angle + (Math.PI * 2 *
(sectorValues[i] / total)), false);
context.lineTo(170, 250);
context.fill();
context.stroke();
angle += Math.PI * 2 * (sectorValues[i] / total);
}
//draw legends
var offset = 150;
for (var i = 0; i < sectorColors.length; i++) {
context.fillStyle = sectorColors[i];
context.font = ‘bold 12px Arial’;
context.save();
context.shadowColor = “silver”;
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.fillRect(400, offset, 20, 15);
context.restore();
context.textBaseline = “middle”;
context.fillText(sectorNames[i] + ’ - ’ + sectorValues[i] + ‘%’, 425, offset + 10);
offset += 30;
}
});`
清单 4-28 中的代码声明了三个全局数组——sectorNames
、sectorValues
和sectorColors
——分别用于存储扇区名、它们的值和它们的颜色。
然后,代码通过检查渐变复选框的状态来确定是否要绘制渐变。相应地,要么绘制线性渐变,要么使用clearRect()
方法清除画布。注意如何使用createLinearGradient()
方法以及如何定义色标。起始色标从相应的文本框中选取,但结束色标始终是白色的。还要注意使用画布的save()
和restore()
方法来保存和恢复画布状态。您可以将画布设置恢复为初始值,因为您不需要为每个绘制操作都使用渐变。
接下来,使用 jQuery each()
方法遍历所有的 HTML 表格行。这三个扇区数组是通过从各自的表格单元格中选取值来填充的。
然后使用fillText()
方法绘制饼图的标题。注意,这个标题有一个使用shadowOffsetX
、shadowOffsetY
和shadowColor
属性配置的阴影。出于前面提到的相同原因,画布状态被恢复到先前的值。
接下来,for
循环遍历sectorValues
数组,并使用arc()
方法绘制饼图的扇区。请注意,角度是如何根据扇区值的总和以及正在绘制的扇区值来计算的。
最后,绘制饼图的图例。相关的for
循环遍历sectorColors
数组,对于每个扇区,用扇区颜色绘制一个矩形。在矩形的前面还绘制了一个标签。
保存饼图
一旦在画布上绘制了饼图,就可以将其保存到数据库中。保存图表按钮的click
事件处理程序包含 jQuery 代码,该代码在$.ajax()
方法的帮助下调用SaveChart()
控制器动作方法。清单 4-29 展示了这是如何做到的。
***清单 4-29。*在服务器上保存饼图
`$(‘#btnSave’).click(function () {
var data = canvas.toDataURL();
data = ‘“data”: "’ + data.replace(‘data:image/png;base64,’, ‘’) + ‘"’;
var master = ‘“master”: {“Title” :"’ + $(“#txtTitle”).val() + ‘"}’;
var details = ‘“details” : [’;
for (var i = 0; i < sectorNames.length; i++) {
var sector = ‘{“SectorName” : "’ + sectorNames[i] + ‘“, “SectorValue”:”’ +
sectorValues[i] + ‘“, “SectorColor”:”’ + sectorColors[i] + ‘"}’;
details += sector
if ((i+1) != sectorNames.length) {
details += “,”;
}
}
details += ‘]’;
var finalData = ‘{’ + data + ‘,’ + master + ‘,’ + details + ‘}’;
$.ajax({
type: ‘POST’,
url: ‘/Chart/SaveChart’,
data: finalData,
contentType: ‘application/json; charset=utf-8’,
dataType: ‘json’,
success: function (result) {
alert(result);
}
});
});`
代码需要从客户端向服务器传递一个复杂的 JSON 对象,因为SaveChart()
方法有三个参数。此外,其中一个参数是一组ChartDetail
对象。清单 4-29 中的代码通过收集各种信息(比如图表标题和扇区细节)构建了一个 JSON 对象(finalData
)。jQuery $.ajax()
方法然后向SaveChart()
方法发出一个POST
请求,并传递 JSON 对象和调用。success
函数在一个警告框中显示从服务器返回的消息。
图 4-26 显示了应用的运行示例。
***图 4-26。*应用成功运行
正如您所看到的,如果您成功地将图表保存在服务器上,就会在一个警告框中显示一条成功消息。您可以通过检查ChartMaster
和ChartDetails
表中的数据是否存在来验证保存操作。
总结
HTML5 画布允许您使用画布 API 绘制直线、曲线、路径、形状、图像和文本。绘图上下文对象提供了执行刚才提到的绘图操作的方法和属性。您还可以为绘图对象添加花哨的装饰,如阴影、渐变和透明度。画布还允许您以编程方式保存画布状态。使用 jQuery 代码,您可以将画布绘图作为 Base64 字符串传输到服务器。在这一章中,你学习了所有这些,并且使用 canvas API、jQuery 和 ASP.NET MVC 开发了一个简单而实用的图表应用。
第三章和第四章讨论了 HTML5 的音频、视频和图形条款。吸引任何开发数据驱动 web 应用的 ASP.NET 开发者的一个领域是表单增强,比如新的输入类型和验证。下一章将剖析这些增强,并展示如何在 ASP.NET web 窗体和 MVC 应用中使用它们。
五、使用窗体和控件
ASP.NET 大放异彩的关键领域之一是开发数据驱动的网络应用。大多数 ASP.NET web 应用不仅仅是 HTML 页面的集合。它们涉及各种数据库任务,从简单的记录列表到复杂的数据库操作。这些任务通常包括接受用户输入,对用户输入的数据执行验证,在服务器上处理数据,最后将其保存在数据存储中。
数据输入页面通常显示一组控件,如文本框、复选框、单选按钮、下拉列表和类似的元素。这些控件包含在一个 HTML <form>
元素中。在填写表单时,用户可以将表单及其数据提交给服务器进行处理。多年来,开发 HTML 表单变得越来越复杂,要求也越来越高。例如,许多表单需要复杂的数据格式和业务验证。他们还需要针对特定数据类型(如日期-时间和数字)的不同类型的控件。考虑到这些不断变化的需求和趋势,HTML5 对现有的<form>
特性提供了一组增强。它还引入了一些新功能,使整个表单开发变得更加容易。本章详细介绍了 HTML5 的这些新的和增强的表单特性。具体来说,您将了解以下内容:
- Use the new HTML5 input type
- Improvements to existing controls
- Using the new and enhanced functions of HTML5 in ASP.NET Web forms and MVC applications
- Validate user input using various techniques.
了解 ASP.NET 的 HTML 表单
无论何时开发数据驱动的 ASP.NET 或 ASP.NET MVC 应用,应用的用户界面都分别借助于 Web 表单和视图来呈现。Web 表单和视图又使用 HTML <form>
元素来存放页面的数据输入区域。在本章深入探讨<form>
元素的 HTML5 特定特性之前,有必要简要讨论一下<form>
元素如何出现在典型的 ASP.NET Web 表单和 ASP.NET MVC 应用中。
ASP.NET Web 表单中的<表单>元素
一个典型的 ASP.NET web 表单由一个标记有runat
服务器的<form>
标签组成。此外,它还承载一个或多个服务器控件,如文本框、列表和网格。最后,它提供了一种向服务器提交表单以供进一步处理的机制。清单 5-1 展示了一个简单的 web 表单,展示了这些元素在源代码视图中的样子。
***清单 5-1。*Web 表单应用中的服务器端<form>
元素
`
<asp:Label ID=“Label1” runat=“server” Text="Enter your Name : " ></asp:Label>
<asp:TextBox ID=“TextBox1” runat=“server”></asp:TextBox>
<asp:Button ID=“Button1” runat=“server” Text=“Submit” />
清单 5-1 中的<form>
标签有两个属性:id
和runat
。如果您希望在服务器上处理一个 HTML 表单,它必须标有runat
。runat
属性只有一个可能的值:server
。在<form>
中有一个Label
服务器控件、一个TextBox
服务器控件和一个Button
服务器控件。如果您在浏览器中运行该 web 表单,您将得到如图图 5-1 所示的显示。
***图 5-1。*显示在浏览器中的简单 web 表单
如果您查看浏览器显示的结果页面的 HTML 源代码,它如清单 5-2 所示。
***清单 5-2。*一个网页表单的 HTML 源码
<form method="post" action="WebForm1.aspx" id="form1"> <div class="aspNetHidden"> <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="kv16mNsClgHGNtkmN…" /> </div> <div class="aspNetHidden"> <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="jIRq9NWtI20O4IJRVRDv7…" /> </div> <span id="Label1" >Enter your Name : </span> <br /> <input name="TextBox1" type="text" id="TextBox1" />
`
正如你所看到的,在运行时,ASP.NET 向生成的 HTML 标记添加了几个部分。注意现在<form>
标签有了action
和method
属性。还要注意,所有的服务器控件都被转换成它们的等效 HTML 标记。最后,注意添加在表单顶部的ViewState
隐藏表单字段。这种庞大的 Base64 编码的ViewState
(为了清楚起见,清单中将其截断)是 ASP.NET 开发人员对 Web 表单的保留意见之一。
以下是关于 web 表单中使用的<form>
标签的一些值得注意的事情:
典型地,一个 web 表单只有一个
<form>
标签,尽管 HTML 没有强加任何这样的限制。* A form method is alwaysPOST
.* A web form is published to itself. That is, theaction
attribute of the<form>
tag points to the same.aspx
file.* When using a master page, the<form>
tag is placed in the master page file (.master
) instead of a separate content page (.aspx
).
<构成了 ASP.NET MVC 中的>元素
ASP.NET MVC 应用上下文中的<form>
元素非常灵活。当您在视图中放置一个<form>
元素时,ASP.NET MVC 应用有几个选项:
- An HTML
<form>
tag and an HTML<input>
tag can be directly placed in a view file.- HTML Assistant can be used to render
<form>
and<input>
tags.
清单 5-3 显示了一个使用普通 HTML 标记开发的表单。
清单 5-3。在 MVC 视图中使用普通的 HTML 标签
`
Enter your Name :
这个<form>
使用普通的 HTML 标记,比如<form>
和<input>
。注意,<form>
标签的action
属性指向一个控制器动作方法(/Home/Index
);您可以自由地将它指向任何控制器类。
清单 5-4 显示了使用 HTML 助手开发的相同表单。
***清单 5-4。*一个使用 HTML 助手渲染的<form>
<% using(Html.BeginForm("IndexWithHelpers","Home","POST")) %> <% { %> <%= Html.Label("Enter your Name :") %> <br /> <%= Html.TextBox("txtName") %>
<br /><br /> <input type="submit" value="Submit" /> <% } %>
如清单 5-4 所示,<form>
标签使用BeginForm()
方法呈现。BeginForm()
方法的三个参数分别代表动作方法名、控制器名和表单方法。Label
和TextBox
HTML 助手分别呈现一个<label>
和一个<input>
标签。视图的结果标记与清单 5-3 中的非常匹配。
HTML5 控件
HTML5 表单由文本和控件的组合组成。HTML5 表单的文本部分通常由字段标签、部分标题和关于表单的说明性文本组成。HTML5 标记提供了过多的控件。这些控制可大致分为以下几类:
- A data input control for inputting string data. Controls such as text box and
<textarea>
belong to this category.- Option control, which allows you to select one or more options from a set of options. Controls such as check boxes, radio buttons and drop-down lists belong to this category.
- A range selector control that allows you to select a value between the minimum and maximum values.
- And a date selector control that allows you to select a date or part of a date (week, day, month, etc.).
- A color selector control that allows you to select color values.
- Control, rendered as a submit, reset, image, or button.
- Miscellaneous controls that do not belong to any of the other categories mentioned here.
HTML 4.01 提供了许多属于这些类别的控件。这里列出的大多数控件都是使用一个<input>
元素显示的。<input>
元素的type
属性负责呈现所需类型的控件。
HTML5 增加了许多新的输入类型;您将在接下来的章节中了解它们。表 5-1 列出了 HTML5 中可用的输入类型,供您快速参考。还列出了一些不是<input>
类型但在 HTML 表单中常用的控件。
下一节讨论 HTML5 引入的新输入类型。
使用特定于 HTML5 的输入类型
确保用户输入的数据符合预期的格式是所有数据驱动的 web 应用中的一项常见任务。传统上,开发人员使用定制的 JavaScript 代码来执行如下检查:
- The entered data is in the correct format: for example, email address, URL and phone number.
- The input data is in a certain range between the minimum and maximum values, such as age group and annual income.
- Date is entered in general format, and the numerical value is the effective date.
毫无疑问,通过 JavaScript 执行这样的验证是一种很好的编程实践。然而,更合理的方法是使数据输入控件能够接受所需格式或范围的数据。这样,您不需要编写任何 JavaScript 代码来验证用户输入。新的 HTML5 输入类型试图做到这一点。但是请记住,并不是所有的浏览器都支持这些新的输入类型。此外,浏览器对特定输入类型的支持可能在控件的用户界面和验证功能方面有所不同。
与 HTML5 的其他特性一样,建议您使用 Modernizr 库检查浏览器对新输入类型的支持。清单 5-5 显示了一些示例 jQuery 代码,用于检查浏览器是否支持email
输入类型。
***清单 5-5。*检查对新输入类型的支持
$(document).ready(function () { if (!Modernizr.inputtypes.email) { alert("This browser doesn't support email input type of HTML5!"); } });
注意如果浏览器遇到无法识别的输入类型,就会呈现一个普通的文本框。在撰写本文时,Chrome 和 Opera 在支持新的输入类型方面处于领先地位。因此,下面讨论的所有输入类型都使用 Chrome 或 Opera 来说明。
请注意,不同的浏览器可能会根据输入类型执行不同的验证。例如,当您离开字段时,Firefox 会突出显示包含无效值的<input>
字段。而 Chrome 和 Opera 则不会给出这样即时的视觉反馈。但是,所有浏览器都会在表单提交时执行验证。此时,浏览器显示第一个出错的<input>
字段的错误消息(如果有的话),并且该输入字段获得焦点。例如,考虑一个有三个<input>
字段的表单,其中前两个包含无效值。当提交这样的表单时,浏览器显示第一个输入字段的验证错误消息,并且第一个输入字段获得焦点。只有在第一个输入字段中输入有效值时,才会显示第二个输入字段的验证错误消息。如果有任何验证错误,表单不会被提交。
下面几节讨论 HTML5 提供的新输入类型。请记住,所有的 HTML 4.01 输入类型都是可用的,可以像以前一样使用。
电子邮件地址
出于从用户注册到联系方式的各种原因,电子邮件地址通常被用在网站上。您可以使用email
输入类型接受电子邮件地址。清单 5-6 展示了如何使用电子邮件输入类型。
***清单 5-6。*使用email
输入类型
<span>Enter your email address :</span> <br /> **<input id="email" type="email" />** <br /> <input type="submit" value="Submit"/>
如您所见,type
属性被设置为email
。如果你试图输入一个无效的电子邮件地址,浏览器会显示一条错误信息(参见图 5-2 )。
图 5-2。 Chrome 在用户输入无效电子邮件地址时显示错误信息
请注意,仅当文本框包含值时,才会显示错误消息。如果文本框为空,则不执行任何验证。这种行为类似于 ASP.NET 验证控件。
・??️ 网址
将输入类型设置为url
可以确保输入的值与 URL 模式相匹配。按如下方式使用url
类型:
<input id="url" type="url" />
如果您输入一个有效的互联网 URL,如[
www.microsoft.com](http://www.microsoft.com)
,输入字段不会给出任何错误。输入无效的互联网 URL,如http://microsoft%com
,会导致浏览器显示错误消息。然而,请注意,大多数浏览器中的url
输入类型的当前实现并不执行对 URL 格式的严格检查。例如,它们允许 URL 中包含空格。
数字和电话号码
数字数据通常在 web 页面中被接受,例如年龄、货币值、组织中的雇员数量等等。在这种情况下,您可以使用number
输入类型。此外,number
输入类型允许您指定数字的可接受范围。例如,考虑一个接受雇员当前工资的网页。下面是如何使用number
输入类型:
<input id="salary" type="number" />
图 5-3 显示了 Chrome 如何将number
输入类型显示为上下控制。
**图 5-3。**Chrome中显示的number
输入类型
***图 5-4。*指定了最小和最大属性的number
输入类型
除了将type
属性指定为number
之外,还可以指定最小值、最大值和step
值。控件应该接受的最小值和最大值分别由min
和max
属性指定。step
属性指示当数字增加或减少时数字的跳跃。以下标记显示了如何使用这三个属性将用户的年龄限制在 18 到 100 岁之间:
<input type="number" min="18" max="100" step="2" />
图 5-4 显示了试图超过最大值时显示的错误信息。
默认情况下,使用number
输入类型只允许输入整数。如果您希望输入小数,您需要指定一个合适的step
值,如下所示:
<input type="number" min="10" max="50" step="0.5" />
注意min
、max
和step
属性与这些输入类型一起工作:number
、range
、date
、datetime
、datetime-local
、month
、time
和week
。
要将输入字段标记为电话号码,可以使用tel
类型。但是目前,浏览器没有对tel
输入类型进行任何特殊的验证,因此它更像是一个标记。您可以使用这些信息来执行定制的 JavaScript 验证。清单 5-7 展示了如何操作。
**清单 5-7。**对tel
输入类型的自定义验证
$("#form1").submit(function (e) { var flag = false; $("input[type='tel']").each(function (i,v) { var pattern = /^(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}$/; var value = $(this).val(); if (!pattern.test(value)) { alert("Telephone no. is invalid!"); flag = true; } }); if (flag) { e.preventDefault(); } });
清单 5-7 中的代码显示了一个 jQuery submit()
函数,它处理表单的submit
事件。当使用提交按钮或以编程方式提交表单时,会引发submit
事件。submit
事件处理函数声明了一个flag
变量,该变量指示输入的电话号码是否与某个模式匹配。
然后,jQuery 属性选择器选择所有属性设置为tel
的输入元素。jQuery each()
方法根据正则表达式检查每个匹配的元素,如模式变量所示。如果参数中指定的值与模式匹配,正则表达式的test()
方法返回true
;否则返回false
。如果test()
方法返回false
,则使用警告框向用户显示一条错误消息。将flag
变量设置为true
表示验证错误。使用preventDefault()
方法阻止默认动作(在本例中是表单提交)。
范围选择器
有时,您需要选择(而不是输入)特定范围内的值。回想一下你在第三章中开发的定制视频播放器:你提供了改变播放器音量的工具。在这种情况下,期望用户输入音量是不合适的。相反,最好让他们从一个范围内选择一个音量级别。以下标记显示了如何使用range
输入类型:
<input id="range1" type="range" min="1" max="5" step="1" />
诸如min
、max
和step
的属性与number
输入类型的意义相同(min
和max
控制控件的最小和最大允许值,step
控制值的跳跃)。图 5-5 显示了先前的范围选择器是如何在 Chrome 中显示的。
***图 5-5。*Chrome 中的范围选择器
结果范围选择器的最小值为 1,最大值为 5,步长为 1。
日期和时间
web 应用中另一种常用的数据类型是日期和/或时间。在 ASP.NET Web 表单中,Calendar
服务器控件允许您选择日期。然而,Calendar
最大的缺点是当选择日期时需要回发。难怪 ASP.NET 的开发人员经常在他们的 web 应用中使用基于 JavaScript 的弹出日期时间选择器。如果浏览器本身能够显示一个日期时间选择器就更好了,这就是date
和time
输入类型出现的地方。使用这些输入类型,您可以选择日期和/或时间。用户还可以选择完整的一周或一个月,而不是特定的一天或时间。
注意在撰写本文时,Opera 是唯一一个为date
和time
输入类型显示弹出日期时间选择器的浏览器。Chrome 只有在输入类型为date
时才会显示弹出的日期选择器;它为其他日期和时间类型呈现一个纯文本框。另外,date
的显示格式也有区别。例如,Opera 以 yyyy-MM-dd 格式显示日期,而 Chrome 则按照机器日期格式显示。
清单 5-8 显示了所有可用的日期时间输入类型。
***清单 5-8。*日期和时间输入类型
<input id="dt1" type="date" /> <input id="dt2" type="time" /> <input id="dt3" type="datetime" /> <input id="dt4" type="datetime-local" /> <input id="dt5" type="week" /> <input id="dt6" type="month" />
六种日期时间输入类型允许您分别接受日期、时间、日期和时间、本地日期和时间、周和月。日期以 yyyy-MM-dd 格式显示,而时间以 hh:mm:ss 格式显示。对于周和月,格式分别为 yyyy-Www 和 yyyy-MM 。图 5-6 显示了 Opera 如何显示这些日期时间输入类型。
***图 5-6。*戏曲显示日期时间输入类型
如果点击图 5-6 中显示的date
输入类型的向下箭头,会显示一个弹出的日期选择器(参见图 5-7 )。
***图 5-7。*显示弹出式日历的歌剧
注意,您还可以使用日期的min
和max
属性将日期选择限制在特定的日期范围内。
颜色
输入类型color
允许您指定颜色值。回忆一下你在第四章的中开发的饼状图画布应用。在这种情况下,您在纯文本框中指定了扇区颜色。如果你的浏览器支持color
输入类型,它可以用颜色选择器提示你,让你的工作变得容易。图 5-8 显示了 Opera 如何显示一个color
输入类型。
***图 5-8。*歌剧中的拾色器
使用color
输入类型只是将<input>
元素的type
属性设置为color
,如下所示:
<input id="color1" type="color" />
一旦用户选择了一种颜色,<input>
字段的值将返回相应的十六进制颜色代码。例如,选择白色会返回#ffffff
。默认情况下,拾色器默认为黑色(#000000
。
搜索
search
输入类型用于搜索框。目前,浏览器没有为search
输入类型提供任何特别的东西,除了一些显示变化。例如,当你开始在搜索框中输入时,Chrome 会显示一个叉号(X)(见图 5-9 )。单击 X 清除搜索框。
***图 5-9。*在 Chrome 中搜索输入类型
现在您已经知道了哪些新的输入类型可用,以及如何在 web 页面中使用它们,是时候看看如何在 Web 表单和 MVC 视图中使用这些输入类型了。
在网络表单中使用新的输入类型
在 ASP.NET 4.5 中,您可以将TextBox
服务器控件的TextMode
属性设置为所需的输入类型。在 ASP.NET 4.5 之前,TextMode
属性只有两个值:SingleLine
和MultiLine
。然而,在 ASP.NET 4.5 中,所有新的输入类型都被支持,这在图 5-10 中很明显。
图 5-10。TextMode
属性支持新的输入类型。
在运行时,ASP.NET 框架将TextMode
设置转换为适当的输入类型属性。注意,您也可以直接在<asp:TextBox>
标记中设置type
属性来获得相同的效果。但是,推荐的方式是设置TextMode
属性。当然,没有什么可以阻止你使用一个普通的 HTML5 <input>
标签,并为它的type
属性设置所需的设置。但是如果您希望从服务器端代码访问文本框,那么您应该使用TextBox
服务器控件。
如果您希望设置min
、max
和step
等属性,您需要将它们直接添加到<asp:TextBox>
标记标签中,如下所示:
<asp:TextBox ID="TextBox1" runat="server" TextMode="Number" min="18" max="100" step="2"> </asp:TextBox>
注意如果你使用的是 Visual Studio 2010,你可能需要安装最新的服务包才能使用新的输入类型。
在 MVC 视图中使用新的输入类型
在 MVC 视图中,使用新的 HTML5 输入类型要简单一些。因为 MVC 视图直接依赖于 HTML 标记而不是服务器控件,所以您可以很容易地使用前面讨论过的<input>
标签。如果您喜欢使用 HTML 助手而不是原始 HTML 标记,可以使用如下新的输入类型:
<%= Html.TextBox("txtNumber","18",new {type="number",min="18",max="100"}) %>
正如您所看到的,TextBox
助手有三个参数:生成的<input>
元素的名称,它的默认值,以及一个表示需要添加到元素中的附加 HTML 属性的对象。在本例中,您设置了type
、min
和max
属性。
如果你的观点强烈反对某个模型,你可以用TextBoxFor()
代替TextBox()
,如下所示:
<%= Html.TextBoxFor(m => m.Age, new {type="number",min="18",max="100"}) %>
在这一行代码中,假设模型有一个名为Age
的属性。属性显示在类型为number
的文本框中。文本框的min
和max
属性分别被设置为18
和100
。
其他验证属性
在前面的小节中,您了解了 HTML5 输入类型,这些类型允许您验证输入数据的格式。除了输入类型之外,HTML5 还添加了几个其他属性,用于控制验证过程的其他一些方面。本节讨论这些技术。
处理必填字段
许多数据输入表单希望在一个或多个文本框中输入值。为了执行这一要求,ASP.NET 开发人员经常使用RequiredFieldValidator
控件。HTML5 包括一个简单的替代方法来指示强制数据输入字段。考虑以下标记:
<input id="firstName" type="text" name="firstName" required="required"/>
注意<input>
元素末尾的required
属性。这是 HTML5 表示给定字段是必填字段的方式。如果您没有为firstName
文本框提供任何值,即使您没有编写任何验证脚本,浏览器也会显示一条错误消息并拒绝提交表单。图 5-11 显示了 Chrome 如何抛出一个错误信息。
***图 5-11。*使用required
属性
如您所见,试图在文本框中不输入值的情况下提交表单会产生一个错误,提示用户填写该字段。
使用正则表达式的模式匹配
新的 HTML5 输入类型允许您验证一些常用的数据格式。然而,有时你需要验证一些更复杂的东西。验证这种复杂格式的常用方法是正则表达式。HTML5 也依赖它们来执行定制的模式匹配操作。让我们看看怎么做。
假设您希望接受用户的名字。名称只能包含大写或小写的字母 A 到 Z 。HTML5 <input>
元素提供了一个pattern
属性,该属性接受任何有效的正则表达式,并根据它验证输入的值。如果输入的值与正则表达式指定的模式不匹配,就会显示一个错误。下面一行标记显示了如何使用pattern
属性:
<input id="firstName" type="text" name="firstName" pattern="^\s*([a-zA-Z]+)\s*$" required="required" />
这个<input>
标签的pattern
属性被设置为一个正则表达式,只允许在文本框中输入字母字符。如果你试图输入一个数字作为名字,你会得到一个错误,如图图 5-12 所示。
**图 5-12。**使用pattern
属性验证数据
在输入字段中输入数字数据会产生错误,因为正则表达式只允许字母字符。默认的错误消息让用户不知道确切的格式是什么或者哪里出错了;稍后,您将学习自定义浏览器显示的错误信息。
打开和关闭 HTML5 验证
当您使用新的输入类型和相关的验证技术时,表单在提交给服务器之前会被验证。但是,有时您可能希望完全跳过表单验证。在测试阶段,您可能想要测试您的服务器端验证,并且您可能想要暂时禁用 HTML5 验证。在某些情况下,不执行任何验证就提交表单可能是没问题的。例如,假设您在表单上有一个“取消”按钮或“帮助”按钮。因为这些按钮指示的操作不依赖于数据,所以您可以安全地绕过验证。
您可以在两个不同的地方关闭 HTML5 验证:
- At the
<form>
label layer- At the submit button level
前一种方法在测试阶段很好,后一种方法适用于需要提交表单而不考虑控件值的情况。
为了关闭表单级别的验证,HTML5 提供了novalidate
属性。在提交按钮级别,HTML5 formnovalidate
属性完成这项工作。以下标记片段显示了如何使用这两个属性:
<form id="form1" method="post" novalidate="novalidate"></form> <input type="submit" formnovalidate="formnovalidate" />
<form>
元素的novalidate
属性被设置为novalidate
。如果您提交此表单,则不会根据输入类型或模式对输入数据进行验证。
提交按钮的formnovalidate
属性被设置为formnovalidate
。这与novalidate
属性具有相同的效果。
执行自定义验证
尽管 HTML5 输入类型以及 required 和 pattern 属性允许您验证数据,但现实世界的 web 应用仍然经常需要复杂的验证。特别是,涉及对数据库中的数据进行验证的验证超出了到目前为止讨论的技术的范围。幸运的是,HTML5 还提供了一种可以用来执行这种定制验证的技术。它包括两个步骤:
- Write a custom JavaScript function that performs custom validation.
- If the verification fails, call the
setCustomValidity()
method on the input field to inform the browser of the error.
2 是您向浏览器提供自定义错误消息的机会,这样浏览器就可以在验证失败时弹出它。为了理解这些步骤是如何工作的,请看清单 5-9 。
***清单 5-9。*自定义验证功能
$(document).ready(function () { $("#btnSubmit").click(OnSubmit); }); function OnSubmit(evt) { $.ajax({ url: '/home/ValidateCustomerID', type:'post', data: { id: $("#txtCustId").val() }, dataType: 'json', async:false, success: function (result) { var textbox = $("#txtCustId").get(0); if (result == false) { textbox.setCustomValidity("Customer ID was not found in the database!"); } else { textbox.setCustomValidity(""); } } }); }
清单 5-9 显示了提交按钮的click
事件处理程序。该事件处理程序使用$.ajax()
调用名为ValidateCustomerID()
的控制器动作,并传递在文本框中输入的客户 ID(txtCustId
)。如果在Northwind
数据库的Customers
表中找到提供的客户 ID,则ValidateCustomerID()
动作返回true
;否则返回false
。注意$.ajax()
调用的success
功能:如果在数据库中没有找到客户 ID,那么将在文本框中调用setCustomValidity()
方法,并显示一条定制的错误消息。提交表单时,向用户显示该错误信息。如果找到了一个客户 ID,调用setCustomValidity()
方法,使用一个空字符串表示没有验证错误。ValidateCustomerID()
动作如清单 5-10 所示。
清单 5-10。 ValidateCustomerID()
动作方法
public JsonResult ValidateCustomerID(string id) { NorthwindEntities db = new NorthwindEntities(); var data = from item in db.Customers where item.CustomerID == id select item; if (data.Count() <= 0) { return Json(false); } else { return Json(true); } }
ValidateCustomerID()
动作使用Northwind
数据库的实体框架数据模型来确定数据库中是否存在一个CustomerID
值。相应地,返回一个true
或false
标志作为JsonResult
。图 5-13 显示了一个带有无效客户 id 的视图运行示例。
图 5-13。 setCustomValidity()
法在行动
注意清单 5-9 中关于的一件有趣的事情:$.ajax()
调用指定了false
的一个async
选项值。默认情况下,$.ajax()
向服务器发出异步请求。然而,使用这个默认行为,提交按钮提交表单,而不等待ValidateCustomerID()
返回。当async
选项设置为false
时,$.ajax()
向服务器发出同步请求而不是异步请求,并且OnSubmit
事件处理程序等待ValidateCustomerID()
方法完成。
HTML5 输入类型和 ASP.NET 验证技术
虽然新的 HTML5 输入类型是对 HTML 标记的巨大补充,但有时您需要混合 HTML5 本地验证技术和 ASP.NET 验证技术。ASP.NET 网络表单和 ASP.NET MVC 分别通过服务器控件和 HTML 助手为验证提供了很好的支持。有必要研究一下这些验证技术如何适应 HTML5。以下部分讨论了 HTML5 验证技术与 ASP.NET 内置技术的各种组合。
HTML5 输入类型和验证控件
ASP.NET Web 窗体提供了用于执行一组常见验证任务的验证控件。这些验证控件发出执行数据验证工作的客户端 JavaScript。更重要的是,您可以将它们与 HTML5 输入类型一起使用。提交表单时,验证控件和 HTML5 输入类型执行验证。如果一个文本框的TextMode
被设置为Email
并且还附加了一个RegularExpressionValidator
(见清单 5-11 ),当提交表单时,两种方案都会显示一条错误消息。
***清单 5-11。*将TextMode
和RegularExpressionValidator
一起使用
`<asp:TextBox ID=“TextBox3” runat=“server” TextMode=“Email”></asp:TextBox>
<asp:RequiredFieldValidator ID=“RequiredFieldValidator3” runat=“server”
ControlToValidate=“TextBox3” ErrorMessage=“Please enter email address”
ForeColor=“Red”>*</asp:RequiredFieldValidator>
<asp:RegularExpressionValidator ID=“RegularExpressionValidator1” runat=“server”
ErrorMessage=“Invalid email address” ForeColor=“Red”
ValidationExpression="\w+([-+.']\w+)@\w+([-.]\w+).\w+([-.]\w+)"
ControlToValidate=“TextBox3”></asp:RegularExpressionValidator>`
图 5-14 显示了这在运行时是如何工作的。
***图 5-14。*验证控件和 HTML5 输入类型一起使用
你可能想知道为什么要一起使用验证控件和 HTML5 输入类型。因为并非所有浏览器都支持 HTML5 输入类型,所以您需要使用验证控件作为后备机制。所以,总之你有以下选择:
- Use only HTML5-specific input types and validation attributes. If the target browser supports HTML5 input type, this method is feasible; Otherwise, no verification is performed. Use the combination of HTML5 authentication technology and ASP.NET authentication control. This method ensures that validation works in all browsers, but as mentioned earlier, there may be some redundancy in the error message. When client authentication is turned off, HTML5 authentication technology and ASP.NET authentication control are used together. In this case, if the target browser supports HTML5, HTML5 verification technology is executed; Otherwise, the form will be posted back to the server and the validation control will validate the data. Of course, in the latter case, the extra round trip to the server is costly.
MVC 应用中的 HTML5 输入类型和服务器端验证
将 HTML5 的新验证技术与 ASP.NET MVC 相结合的一种方法是使用 HTML5 提供客户端验证,同时使用 ASP.NET MVC 在控制器中提供服务器端验证。在这个例子中,您使用 MVC ValidationMessage
和ValidationSummary
HTML 助手来验证名字、姓氏和电子邮件值。清单 5-12 显示了一个简单表单的视图,该表单有三个字段:FirstName
、LastName
和Email
。
***清单 5-12。*MVC 应用中的验证助手和 HTML5 输入类型
`<% using (Html.BeginForm()) { %>
<%= Html.Label("First Name :")%> | <%= Html.TextBox("FirstName", "", new {required="required"})%> <%= Html.ValidationMessage("FirstName","*")%> |
<%= Html.Label("Last Name :")%> | <%= Html.TextBox("LastName", "", new {required="required"})%> <%= Html.ValidationMessage("LastName","*")%> |
` ` <%= Html.Label("Email Address :")%> | <%= Html.TextBox("Email", "", new {type="email"})%> <%= Html.ValidationMessage("Email","*")%> |
如您所见,Email
文本框将其类型设置为email
。还要注意ValidationMessage
和ValidationSummary
MVC HTML 助手是如何显示验证错误的。HTML5 验证就发生在浏览器中。然而,MVC 验证是在控制器中执行的,如清单 5-13 所示。
***清单 5-13。*在控制器中执行验证
[HttpPost] public ActionResult Index(FormCollection form) { if (form["FirstName"].Length < 3 || form["firstname"].Length > 50) { ModelState.AddModelError("FirstName", "Invalid First Name. Must be between 3 and 50 characters."); } if (form["LastName"].Length < 3 || form["LastName"].Length > 50) { ModelState.AddModelError("LastName", "Invalid Last Name. Must be between 3 and 50 characters."); } if (form["Email"].Length <= 0) { ModelState.AddModelError("Email", "Please enter Email Address."); } if(!Regex.IsMatch(form["Email"],@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*")) { ModelState.AddModelError("Email", "Invalid Email Address."); } return View(); }
如您所见,Index()
动作方法对表单数据执行验证,并相应地在ModelState
中添加错误消息。验证助手在浏览器中显示这些模型错误(参见图 5-15 )。
***图 5-15。*MVC 控制器中的验证
当您在客户端和服务器端都有验证逻辑时,客户端验证会在提交表单之前执行。在这个例子中,首先执行 HTML5 验证,然后才提交表单。一旦控制到达Index()
动作方法。执行服务器端验证。
HTML5 输入类型和无障碍验证
无阻碍验证是由 ASP.NET 实现的一种技术,它使用数据注释属性和 jQuery。在这种模式下,数据模型用数据注释属性来修饰,这些属性对数据模型属性值执行验证。在运行时,ASP.NET 发出data-*
属性,这是 HTML5 的一个特性。ASP.NET 在这些发出的data-*
属性和 jQuery 的帮助下执行客户端验证。
data-*
属性是定制属性,就使用语法而言,就像任何其他内置的 HTML 属性一样。但是,它们不同于内置的 HTML 属性,因为它们在元素的可视化呈现中不起任何作用。ASP.NET 4.5 Web 表单和 MVC 应用都支持无阻碍验证。我不再进一步讨论无阻碍的验证方案;可以说,如果需要的话,您可以使用无阻碍验证和 HTML5 验证技术。
注意data-*
属性是 HTML5 的一个特性。你会在《??》第六章中学会使用它们。
定制验证消息
到目前为止,您一直依赖浏览器来显示 HTML5 验证消息。毫无疑问,支持 HTML5 验证的浏览器在整洁地显示验证消息方面做得很好。但是,您可能想要进一步自定义它们。例如,您可能希望错误信息的字体和颜色与您的网页主题相匹配,或者您可能希望突出显示任何违反有效性规则的输入控件。HTML5 提供了两种方法来完成这种定制:
- Use CSS pseudo-class to customize the appearance of input fields that violate validation rules.
- Use JavaScript/jQuery code to customize the display mode of input field verification messages.
前一种技术适用于您希望自定义输入字段的外观,并且对更改验证消息的显示方式不感兴趣的情况。后一种方法使您可以完全控制验证消息的显示方式和位置。它包括处理输入字段的invalid
事件处理程序和编写定制逻辑来显示验证消息。
使用 CSS 伪类定制输入字段的外观
每当出现验证错误时,浏览器都会弹出一个显示验证错误消息的标注(参见图 5-2 )。虽然浏览器不允许您自定义验证消息标注的外观,但是您可以自定义当违反验证规则时输入字段的外观。诀窍是编写特定的 CSS 伪类。
注意CSS伪类是一个基于元素状态应用于元素的类。例如,一个锚(<a>
)标签有伪类link
、visited
、hover
和active
,它们代表超链接的相应状态。本节讨论的 CSS 伪类来自 CSS3 规范。
表 5-2 列出了可以用于 HTML5 验证的伪类。
清单 5-14 使用了表 5-2 中提到的许多 CSS 伪类来定制发生验证错误时输入字段的外观。
***清单 5-14。*定制输入字段的样本伪类
input:invalid { background-color: #ffd800; border: 2px solid #f00; } input:required { background-color: #ffd800; border: 2px solid #f00; } input:out-of-range {
color: #fff; background-color: #242a59; border: 2px solid #f00; }
图 5-16 显示了一个带有无效数据的网页的运行示例。
***图 5-16。*动作中的验证伪类
请注意 Email 文本框是如何选择input:invalid
类的,因为该字段的值不符合有效电子邮件地址的格式。同样,当年龄超过最大限制时,年龄文本框选择input:out-of-range
类。
通过代码定制验证消息
在执行验证之后,HTML DOM 为所有违反验证规则的输入字段引发一个invalid
事件。您可以捕获invalid
事件并编写定制代码来隐藏默认的验证消息并显示您自己的消息。在invalid
事件处理程序中,您可以使用ValidityState
对象来查找关于错误的更多信息。清单 5-15 展示了这是如何实现的。
***清单 5-15。*处理invalid
事件
`KaTeX parse error: Expected '}', got 'EOF' at end of input: …unction () { (“#txtEmail”).bind(“invalid”, OnInvalid);
$(“#txtAge”).bind(“invalid”, OnInvalid);
});
function OnInvalid(evt) {
var input = evt.target;
var validity = input.validity;
if (validity.typeMismatch) {
alert(“Invalid email address”);
}
if (validity.rangeOverflow) {
alert(“Age value too big”);
}
if (validity.rangeUnderflow) {
alert(“Age value too small”);
}
evt.preventDefault();
}`
如您所见,jQuery bind()
方法为两个<input>
字段绑定了invalid
事件处理程序。OnInvalid()
函数通过输入字段的validity
属性获取ValidityState
对象。对象提供了几条关于输入字段当前有效性状态的信息。表 5-3 列出了可以使用的ValidityState
对象的属性。
然后,代码显示一个带有自定义验证消息的警告框。通过调用事件对象的preventDefault()
方法,浏览器的默认验证消息被取消。如果除了警告框之外,您没有使用preventDefault()
调用来阻止默认操作,浏览器的验证错误消息标注也会显示,这在大多数情况下是不必要的。
图 5-17 显示了代码的运行示例。
**图 5-17。**来自invalid
事件处理程序的定制验证消息
如您所见,输入一个无效的电子邮件地址并单击 Submit 按钮会在一个警告框中显示验证错误消息,而不是浏览器的默认标注。
对<输入>元素的其他改进
在前面的章节中,您了解了 HTML5 中可用的新输入类型。HTML5 还通过一些特性增强了<input>
元素:
- Set focus to field when loading form
- Use placeholders to display tips or help text
- Spell check the entered text.
- Or turn on or off the browser autocomplete function.
- Provide a pick list for input fields.
下面几节将讨论这些增强功能。
设置自动对焦
当网页加载到浏览器中时,最好将初始焦点设置到用户应该填写的数据输入字段。您可以使用autofocus
属性来指示输入字段应该获得焦点。以下标记显示了如何操作:
<input id="firstName" type="text" name="firstName" autofocus="autofocus" required="required"/>
这行标记声明了一个文本输入字段,该字段的autofocus
属性被设置为autofocus
。当您在浏览器中打开网页时,firstName
文本框会获得焦点。
使用占位符显示帮助文本
在填写电子邮件地址、URL、日期和电话号码等字段时,建议让用户知道输入数据的格式。一种方法是简单地在数据输入字段前面放置说明性文本。这种方法的缺点是标签会占用额外的屏幕空间。HTML5 占位符提供了一种向输入控件添加提示或帮助文本的优雅方式。
占位符显示为水印,一旦用户开始填充字段,它就会被删除。使用<input>
元素的placeholder
属性指定一个占位符。下面一行标记添加了一个占位符,告诉用户如何设置电话号码的格式:
<input id="telephone" type="tel" placeholder="(123) 123-1234"/>
图 5-18 显示了这个占位符在运行时的样子。
如您所见,输入字段显示了在placeholder
属性中指定的占位符。一旦您开始键入电话号码,占位符文本就会消失。如果输入字段为空,占位符会再次出现。
***图 5-18。*显示占位符
启用拼写检查
如果您的表单接受来自用户的自由格式文本输入,您可能希望对内容启用拼写检查。例如,假设您正在开发一个允许用户发布和编辑文章的博客引擎。如果能对内容进行拼写检查就好了。这样,用户可以很容易地发现拼写错误。属性为您做了这项工作。以下是您使用它的方法:
<textarea id="textarea1" rows="5" cols="50" spellcheck="true"></textarea>
spellcheck
属性是一个布尔值,可以设置为true
或false
。如果设置为true
,<textarea>
(或<input>
)高亮显示拼写错误,如图图 5-19 所示。
***图 5-19。*为<textarea>
启用拼写检查
注意不正确的拼写是如何用红色下划线突出显示的。
关闭自动完成功能
大多数浏览器试图在自动完成功能的帮助下帮助用户填写数据输入表单。该功能允许用户从列表中选择一个值,而不是键入整个值。但是,有时您可能希望对输入字段禁用此功能。属性允许您这样做。以下标记显示了它的用法:
<input id="firstName" type="text" autocomplete="off"/>
当然,你可以将autocomplete
设置为on
来明确开启该功能。您还可以通过将autocomplete
属性添加到<form>
标签而不是单个的<input>
标签来打开或关闭整个表单的自动完成功能:
<form id="form1" autocomplete="off"></form>
如果您设置了<form>
标签的autocomplete
属性,那么属于该表单的所有输入字段的自动完成功能都将被禁用。
提供下拉列表
有时,您希望让用户手动输入一个值,或者从列表中选择一个值。对于桌面应用,组合框控件通常用于此类功能。假设您正在开发一个提供客户详细信息的订单输入页面。如果订单是由现有客户下的,让用户从列表中选择现有的详细信息而不是再次输入它们会很有帮助。您可以使用<input>
和<datalist>
元素的list
属性的组合来提供这样一个列表。清单 5-16 展示了这是如何实现的。
***清单 5-16。*提供下拉列表
<input id="country" type="text" list="pickuplist1" /> <datalist id="pickuplist1"> <option label="India" value="India"></option> <option label="USA" value="USA"></option> <option label="UK" value="UK"></option> </datalist>
如您所见,<datalist>
元素本质上提供了一个键值对列表。value
属性决定了做出选择时目标文本框中应该填充的内容。label
属性作为相应值的描述。<input>
元素的list
属性被设置为<datalist>
的 ID。运行时,显示一个列表,如图图 5-20 所示。
***图 5-20。*输入字段的下拉列表
下拉列表中左边的列显示了在value
属性中指示的值,右边的列显示了由label
属性指定的值。
设置表单的动作和方法
通常,<form>
标签有action
和method
属性,分别表示表单动作和 HTTP 方法(GET
/ POST
)。在某些情况下,您可能希望更改单个提交按钮的这些设置。您可以使用<input>
元素的formaction
和formmethod
属性来实现。清单 5-17 显示了如何操作。
**清单 5-17。**使用<input>
标签设置表单的动作和方法
`
<form>
标签的action
属性设置为/Home/Index
,其method
属性设置为get
。通过将formaction
属性设置为/Home/Index2 并将formmethod
属性设置为post.
,您可以通过<input>
标签覆盖这些默认值。表单上的其他提交按钮(如果有)使用表单级别指定的动作和方法。
设计员工数据表单
现在是时候运用 HTML5 表单特性的知识,开发一个完整的表单来处理来自 SQL Server 数据库的数据了。在本节中,您将开发一个 ASP.NET Web 窗体应用,该应用将数据插入到数据库Northwind
的Employees
表中,在其中更新数据,以及从其中删除数据。应用的主窗体如图图 5-21 所示。
***图 5-21。*员工数据表
图 5-21 所示的表格,为了简单起见,没有更新Employees
表的所有列。员工数据输入表单具有以下验证和功能:
- Columns
FirstName
,LastName
,BirthDate
,Title
(current name),HireDate
,Address
,City
,Country
andPostalCode
are required fields, which must be entered when adding or modifying data.- The legal age for employment is assumed to be 18 years old, so there is a difference of at least 18 years between
BirthDate
andHireDate
.- Show a placeholder when all data entry fields are blank.
- The current designation can be entered manually or selected from the list.
PostalCode
andHomePhone
must match the patterns of U.S. postal code and U.S. telephone number respectively.
配置 FormView 控件
图 5-21 所示的表单实际上是一个FormView
服务器控件。清单 5-18 显示了需要配置的FormView
的重要属性。
清单 5-18。FormView
控件的属性
`<asp:FormView ID=“FormView1” runat=“server”
AllowPaging=“True”
DefaultMode=“Edit”
DataKeyNames=“EmployeeID”
SelectMethod=“GetEmployees”
InsertMethod=“InsertEmployee”
UpdateMethod=“UpdateEmployee”
DeleteMethod=“DeleteEmployee”
ItemType=“SampleAppWebForms.Model.Employee”
…
</asp:FormView>`
默认情况下,当 web 表单显示在浏览器中时,FormView
处于编辑模式,因此您可以修改员工记录。您可以通过将DefaultMode
属性设置为 Edit 来实现。
与依赖于EntityDataSource
控件(或任何其他数据源控件)不同,FormView
依赖于自定义方法来执行创建、读取、更新和删除(CRUD)操作。您可以通过将四个属性SelectMethod
、InsertMethod
、UpdateMethod
和DeleteMethod
设置为适当的方法来实现这一点。GetEmployees()
、InsertEmployee()
、UpdateEmployee()
和DeleteEmployee()
方法是在 web 表单的代码隐藏文件中编写的,将在后面讨论。
FormView
模板被强类型化为Employee
模型的各种属性。这就是为什么ItemType
属性被设置为实体框架数据模型类的全限定名:Employee
。稍后将讨论Employee
模型以及前面提到的 CRUD 方法。
使用 HTML5 输入类型和相关属性
FormView
控件使用EditItemTemplate
和InsertItemTemplate
分别在编辑和插入模式下呈现数据输入表单的用户界面。这些模板在 HTML 和服务器控件标记方面几乎完全相同。清单 5-19 显示EditItemTemplate
。为了可读性,只显示了与 HTML5 验证技术相关的标记。
***清单 5-19。*之EditItemTemplate
之FormView
之
`
Basic Details仔细观察清单 5-19 中显示的标记。总共有三个<fieldset>
元素代表表单的三个部分:基本信息、雇佣信息和联系信息。一个<fieldset>
元素用于对相关元素进行分组。为了直观地表示分组,<fieldset>
元素还绘制了一个包围分组元素的方框。因为这是一个编辑模式模板,所以您希望对任何字段所做的更改传播回数据库。因此,除了EmployeeID
之外的所有字段都使用一个BindItem
对象与数据模型绑定。EmployeeID
是主键,所以它是只读的,因此使用了一个Item
对象。
如果你看一下<asp:TextBox>
元素,你会发现它们中的很多都使用 HTML5 输入类型和相关属性。BirthDate
和HireDate
文本框的TextMode
属性被设置为Date
。同样,HomePhone
的TextMode
值为Phone
。PostalCode
文本框的pattern
属性被设置为匹配美国邮政编码的正则表达式。注意,您也可以为HomePhone
文本框使用pattern
属性,但是应用通过 jQuery 代码来验证电话号码。
一个文本框获得焦点,<datalist>
就显示现有的值。使用list
属性将<datalist>
链接到文本框。实际的<datalist>
项在运行时使用 jQuery 填充。
为所有必须填充的文本框设置了required
属性。此外,它们都将自己的Placeholder
属性设置为一些帮助文本。三个Button
服务器控件— btnUpdate
、btnNew
和btnDelete
—调用适当的动作。注意,这些按钮控件的CommandName
属性分别被设置为Update
、New
和Delete
。这样,FormView
控件触发适当的方法。还要注意的是,btnNew
和btnDelete
已经设置了它们的formnovalidate
属性,因为单击这些按钮时不需要执行数据验证。
注意日期格式
BirthDate
和HireDate
文本框将TextMode
设置为Date
。正如您之前了解到的,HTML5 date
输入类型的默认日期格式是 yyyy-MM-dd 。然而,Employees
表以 MM/dd/yyyy 格式存储日期。为了处理这种日期格式的不匹配,您需要处理FormView
的DataBound
事件,并将日期从 MM/dd/yyyy 格式转换为 yyyy-MM-dd 格式。清单 5-20 显示了如何进行这种转换。
***清单 5-20。*转换日期格式
protected void FormView1_DataBound(object sender, EventArgs e) { if(FormView1.CurrentMode == FormViewMode.Edit) { DateTime dtBirthDate= ((Employee)FormView1.DataItem). BirthDate.GetValueOrDefault(); DateTime dtHireDate = ((Employee)FormView1.DataItem). HireDate.GetValueOrDefault(); TextBox txtBirthDate= (TextBox)FormView1.FindControl("txtBirthDate"); TextBox txtHireDate = (TextBox)FormView1.FindControl("txtHireDate"); txtBirthDate.Text = dtBirthDate.ToString("yyyy-MM-dd"); txtHireDate.Text = dtHireDate.ToString("yyyy-MM-dd"); } }
这段代码检查FormView
的CurrentMode
是否是Edit
,如果是,抓取被绑定的BirthDate
和HireDate
值。DataItem
对象表示与FormView
绑定的Employee
。使用FindControl()
方法找到显示BirthDate
和HireDate
的文本框。最后,使用 yyyy-MM-dd 格式将日期值填入文本框。
编写 CRUD 方法
web 窗体代码隐藏包含四个对数据库执行 CRUD 操作的方法。这些方法如清单 5-21 所示。
***清单 5-21。*执行 CRUD 操作的方法
`public IQueryable GetEmployees()
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Employees
orderby item.EmployeeID
select item;
return data;
}
public void InsertEmployee(Employee e)
{
NorthwindEntities db = new NorthwindEntities();
db.Employees.AddObject(e);
db.SaveChanges();
}
public void UpdateEmployee(Employee e)
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Employees
where item.EmployeeID == e.EmployeeID
select item;
Employee obj = data.SingleOrDefault();
obj.TitleOfCourtesy = e.TitleOfCourtesy;
obj.FirstName = e.FirstName;
obj.LastName = e.LastName;
obj.BirthDate = e.BirthDate;
obj.Title = e.Title;
obj.HireDate = e.HireDate;
obj.Address = e.Address;
obj.City = e.City;
obj.Country = e.Country;
obj.HomePhone = e.HomePhone;
db.SaveChanges();
}
public void DeleteEmployee(Employee e)
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Employees
where item.EmployeeID == e.EmployeeID
select item;
Employee obj = data.SingleOrDefault();
db.DeleteObject(obj);
db.SaveChanges();
}`
这些 CRUD 方法很简单。GetEmployees()
方法将Employee
对象的集合返回给调用者。InsertEmployee()
、UpdateEmployee()
和DeleteEmployee()
方法接收一个Employee
对象作为参数。然后这个Employee
对象被插入到数据库中,在数据库中被更新,或者从数据库中被删除。Employee
模型类包含了Employees
表的所有列,但是您并没有使用它们。因此,UpdateEmployee()
分配了Employee
对象的单个属性。Employee
型号类别如图图 5-22 所示。
图 5-22。 Employee
数据模型类
应用没有使用Employee
类的所有属性。感兴趣的属性有EmployeeID
、TitleOfCourtesy
、FirstName
、LastName
、BirthDate
、Title
、HireDate
、Address
、City
、Country
、HomePhone
。
jQuery Code
web 表单还需要一些 jQuery 代码来完成以下任务:
- From the
Employees
table- Get the existing
Title
(title) to fill in the<datalist>
item, and verify that the employee is at least 18 years old at the time of employment.- Verify the phone number and American phone number format
匹配
要填充<datalist>
项,您需要对服务器进行 Ajax 调用,并检索所有现有的Title
值。你可以使用$.ajax()
方法,如清单 5-22 所示。
**清单 5-22。**用现有的Title
值填充<datalist>
$(document).ready(function () { $.ajax({ url: 'employeeform.aspx/GetTitles', type: 'post', dataType: 'json', contentType: "application/json; charset=utf-8", success: function (results) { for (var i = 0; i < results.d.length; i++) { $("#lstTitles").append("<option label='" + results.d[i] + "' value='" + results.d[i] + "'></option>"); } }, error: function (err) { alert(err.status + " - " + err.statusText); } }); ...
$.ajax()
方法调用驻留在EmployeeForm.aspx
web 表单中的 web 方法GetTitles()
。GetTitles()
返回现有标题的字符串数组。一旦标题被返回,就使用 jQuery append()
方法将它们添加到<datalist>
中。web 方法如清单 5-23 所示。
清单 5-23。 GetTitles()
Web 方法
[WebMethod] public static string[] GetTitles() { NorthwindEntities db=new NorthwindEntities(); var data = (from item in db.Employees select item.Title).Distinct(); return data.ToArray(); }
GetTitles()
从Employees
表中选择不同的Title
值,并将它们作为数组返回给调用者。图 5-23 显示了<datalist>
在运行时的样子。
图 5-23。 <datalist>
使用 jQuery 填充
为了验证雇员在被雇用时至少 18 岁,您需要找出BirthDate
和HireDate
之间的差异。如果这种差异大于 18 年,可以添加或更新员工记录。HomePhone
文本框类型为tel
(Phone
的TextMode
),输入的电话号码应为有效的美国电话号码。当添加或更新记录时,会执行这两种验证。清单 5-24 显示了这是如何完成的。
***清单 5-24。*验证年龄要求和电话号码格式
`
(
"
i
n
p
u
t
[
n
a
m
e
("input[name
("input[name=‘btnUpdate’]“).click(function (e) {
var birthDate = ToDate(
(
"
i
n
p
u
t
[
n
a
m
e
("input[name
("input[name=‘txtBirthDate’]”).val());
birthDate.setFullYear(birthDate.getFullYear() + 18);
var hireDate = ToDate(
(
"
i
n
p
u
t
[
n
a
m
e
("input[name
("input[name=‘txtHireDate’]“).val());
var txtBirthDate =
(
"
i
n
p
u
t
[
n
a
m
e
("input[name
("input[name=‘txtBirthDate’]”).get(0);
if ((hireDate.getTime() - birthDate.getTime()) < 0) {
txtBirthDate.setCustomValidity(“Invalid Birth Date or Hire Date!”);
}
else {
txtBirthDate.setCustomValidity(“”);
}
var pattern = /^(?(\d{3}))?[- ]?(\d{3})[- ]?(\d{4})$/;
var value =
(
"
i
n
p
u
t
[
n
a
m
e
("input[name
("input[name=‘txtPhone’]“).val();
var txtPhone =
(
"
i
n
p
u
t
[
n
a
m
e
("input[name
("input[name=‘txtPhone’]”).get(0);
if (!pattern.test(value)) {
txtPhone.setCustomValidity(“Invalid Telephone Number!”);
}
else {
txtPhone.setCustomValidity(“”);
}
});
function ToDate(input) {
var parts = input.match(/(\d+)/g);
return new Date(parts[0], parts[1] - 1, parts[2]);
}`
一个以结束的 jQuery 属性选择器用于选择btnUpdate
。这是必要的,因为更新按钮是EditItemTemplate
的一部分,并且FormView
为组成控件分配一个唯一的名称(例如,FormView1$btnUpdate
)。第一个代码块主要计算HireDate
和BirthDate
之间的差异。如果这个差值小于 0,说明年龄小于 18 岁。如果是这样,通过调用BirthDate
文本框上的setCustomValidity()
方法添加一个定制的错误消息。
第二个代码块验证电话号码。这是通过将输入的值与正则表达式进行匹配来实现的。注意test()
的使用,它执行模式匹配。如果值与模式不匹配,test()
方法返回false
。在这种情况下,调用setCustomValidity()
方法来设置定制的验证消息。
CSS 伪类
最后,您需要添加一些伪类来突出验证错误。这些伪类在StyleSheet.css
中找到,并在清单 5-25 中显示。
***清单 5-25。*验证错误的伪类
`input:invalid {
background-color: #ffd800;
border: 2px solid #f00;
}
input:required {
background-color: #fff;
border: 1px solid #ff6a00;
}`
invalid
伪类应用于所有无效的输入字段,而required
伪类应用于所有设置了required
属性的输入字段。
就这样!图 5-24 显示了一个带有验证错误的 web 表单的运行示例。
请注意 web 表单是如何在提交表单时验证家庭电话字段的。家庭电话的格式必须是(123) 123-1234。但是,因为输入的电话号码的最后一部分包含 5 位数字而不是 4 位,所以会显示验证错误。
***图 5-24。*出现验证错误的员工数据输入表单
设计用户注册表单
在本节中,您将开发一个显示用户注册页面的 ASP.NET MVC 应用。与您在上一节中开发的员工数据输入表单不同,用户注册页面在客户端和服务器端执行验证。客户端验证使用您在本章中学习的 HTML5 特性来执行,而服务器端验证使用数据注释属性。此外,使用invalid
事件处理客户端验证,以便验证消息(客户端和服务器)以一致的方式显示。图 5-25 显示用户注册页面。
***图 5-25。*用户注册页面
此页面具有以下验证和功能:
- The display name, email address, password, confirmation password and legal age fields must be filled in.
- Display name and email address cannot be duplicated.
- The e-mail address should be in the correct format.
- Password and confirmation password values must match.
- Blog websites must be in the correct format.
- The legal age must be between 18 and 100.
- Spelling check should be turned on in the profile text area.
- Display name, email address, password, blog/website and profile fields have minimum and maximum lengths.
除了最后一项,所有功能都是通过 HTML5 实现的。最后一组验证在服务器上实现。
实体框架数据模型和数据标注
示例页面将用户信息存储在名为Users
的 SQL server 表中。实体框架数据模型用于在Users
表中添加用户数据。图 5-26 显示了User
模型类。
图 5-26。 User
模特班
要执行服务器端验证,您需要使用数据注释属性。不要用数据注释来修饰User
模型类,建议使用元数据类。一个元数据类是一个包含主类各种属性的数据注释的类。然后,元数据类与主数据模型类相关联。使用元数据类的优点是它将特定于验证的元数据隔离在一个单独的类中。这样,即使由于某种原因重新生成了User
模型类,数据注释也不会丢失。元数据类如清单 5-26 所示。
清单 5-26。 User
元数据类
`public class UserMetadata
{
[Required]
[StringLength(50,MinimumLength=3)]
[DisplayName(“Display Name”)]
public string DisplayName { get; set; }
[Required]
[StringLength(250, MinimumLength = 10)]
[DisplayName(“Email Address”)]
public string Email { get; set; }
[Required]
[StringLength(20, MinimumLength = 6)]
public string Password { get; set; }
[StringLength(50, MinimumLength = 10)]
[DisplayName(“Blog / Website”)]
public string BlogUrl { get; set; }
[StringLength(500,ErrorMessage = “Bio size out of permissible limits.”)]
[DisplayName(“Profile”)]
public string Bio { get; set; }
[Required]
[DisplayName(“Legal Age”)]
public string Age { get; set; }
[Required]
[DisplayName(“Yearly Income”)]
public string Income { get; set; }
}
[MetadataType(typeof(UserMetadata))]
public partial class User
{
}`
如您所见,UserMetadata
类包含与User
模型类匹配的属性定义。然后用数据注释属性来修饰这些属性,比如[Required]
、[StringLength]
和[DisplayName]
。正如您可能已经猜到的那样,[Required]
属性确保属性值被设置。属性让您指定字符串属性的最小和最大长度。属性用于为属性指定更友好的名称。这些名称在服务器端错误信息中使用,而不是实际的属性名称。
UserMetadata
类是一个独立的类。为了将它与User
模型类链接起来,您需要创建一个User
分部类并用[MetadataType]
属性来修饰它。
注意数据注释属性在实现模型级别的通用验证标准方面做得很好。然后,ASP.NET 验证机制使用这些信息向用户提供有关验证错误的可视反馈。对数据注释属性的详细讨论超出了本书的范围。
用户控制器
用户注册应用有一个控制器(User
),它包含两种不同的Index()
动作方法。这些动作方法如清单 5-27 所示。
清单 5-27。User
控制器的分度动作方法
public ActionResult Index() { return View(); }
[HttpPost] public ActionResult Index(User user) { if (ModelState.IsValid) { UserDbEntities db = new UserDbEntities(); db.Users.AddObject(user); db.SaveChanges(); return View("Success"); } else { return View("Index", user); } }
Index()
动作方法简单明了。第二个Index()
动作方法接受一个User
实例,并将其添加到数据库中。注意ModelState.IsValid
属性的使用,只有当数据模型(User
对象)满足所有验证标准时,它才返回true
。如果用户注册成功,将呈现成功视图。否则,将显示带有验证错误的索引视图。
User
控制器还包含两个从 jQuery 代码调用的动作方法。这些方法检查重复的显示名称和电子邮件地址,如果发现重复,则返回true
。清单 5-28 展示了这些动作方法。
***清单 5-28。*检查重复的显示名称和电子邮件地址
`[HttpPost]
public JsonResult IsDuplicateEmail(string email)
{
UserDbEntities db = new UserDbEntities();
var data = from item in db.Users
where item.Email == email
select item;
bool flag=false;
if (data.Count() > 0)
{
flag = true;
}
return Json(flag);
}
[HttpPost]
public JsonResult IsDuplicateDisplayName(string displayname)
{
UserDbEntities db = new UserDbEntities();
var data = from item in db.Users
where item.DisplayName == displayname
select item;
bool flag = false;
if (data.Count() > 0)
{
flag = true;
}
return Json(flag);
}`
IsDuplicateEmail()
方法接受要验证的电子邮件地址,如果它已经存在于数据库中,则返回true
。类似地,IsDuplicateDisplayName()
接受一个显示名称,如果它已经存在,则返回true
。注意,这两种方法在将值 it 包装成一个JsonResult
对象后都返回true
或false
。
在 MVC 视图中显示用户注册表单
用户注册表单是使用 MVC HTML 助手呈现的。显示表单的索引视图是用User
类强类型化的。清单 5-29 显示了表单的标记。注意,为了节省空间,表标记和 jQuery 代码都被省略了,用省略号表示。
***清单 5-29。*使用 HTML 助手呈现用户注册表单
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage <SampleAppMVC.Models.User>" %> ... <% using (Html.BeginForm("Index","User","POST")) { %> <%= Html.LabelFor(model => model.DisplayName)%> ... <%= Html.TextBoxFor(model=>model.DisplayName, new {required="required"})%> <%= Html.ValidationMessageFor(model=>model.DisplayName,"*")%> ... <%= Html.LabelFor(model=>model.Email)%> ... <%= Html.TextBoxFor(model=>model.Email, new {required="required",type="email"})%> <%= Html.ValidationMessageFor(model=>model.Email,"*")%> ... <%= Html.LabelFor(model=>model.Password)%> ... <%= Html.PasswordFor(model=>model.Password,new {required="required"})%> <%= Html.ValidationMessageFor(model=>model.Password,"*")%> ... <%= Html.Label("Confirm password")%> ... <%= Html.Password("ConfirmPassword", "",new {required="required"})%> <%= Html.ValidationMessage("ConfirmPassword","*")%> ... <%= Html.LabelFor(model=>model.BlogUrl)%> ... <%= Html.TextBoxFor(model=>model.BlogUrl, new {type="url"})%> <%= Html.ValidationMessageFor(model=>model.BlogUrl,"*")%> ... <%= Html.LabelFor(model=>model.Age)%> ...
<%= Html.TextBoxFor(model=>model.Age, new {required="required",type="number",min="18",max="100"})%> <%= Html.ValidationMessageFor(model=>model.Age,"*")%> ... <%= Html.LabelFor(model=>model.Income)%> ... <%= Html.TextBoxFor(model=>model.Income, new {type="range",min="2",max="20",step="4"})%> <%= Html.ValidationMessageFor(model=>model.Income,"*")%> ... <%= Html.LabelFor(model=>model.Bio)%> ... <%= Html.TextAreaFor(model=>model.Bio, new {spellcheck="true",rows="3",cols="30"})%> <%= Html.ValidationMessageFor(model=>model.Bio,"*")%> ... <input id="btnSubmit" type="submit" value="Submit" /> ... <%= Html.ValidationSummary() %> <div id="divErr" class="validation-summary-errors"></div> ... <%}%>
清单 5-29 使用了许多 HTML 助手方法,比如LabelFor
、TextBoxFor
、ValidationMessageFor
和ValidationSummary
。注意 HTML5 输入类型和属性是如何在 HTML helper 方法的第二个参数中指定的。本节开头列出的验证条件是使用 HTML5 属性实现的。服务器端验证错误通过ValidationSummary()
方法显示。验证摘要下面的<div>
元素用于显示 HTML5 验证消息。
检查重复的显示名称和电子邮件地址
为了确保输入的显示名称和电子邮件地址不存在于数据库中,您需要从 jQuery 代码中调用IsDuplicateDisplayName()
和IsDuplicateEmail()
方法,如前所述。当单击提交按钮时,使用 jQuery $.ajax()
方法调用它们。清单 5-30 展示了如何检查重复的显示名称。
***清单 5-30。*检查重复显示名称
if ($("#DisplayName").val() != "") { var data = '{ "displayname" : "' + $("#DisplayName").val() + '"}'; $.ajax({ url: '/User/IsDuplicateDisplayName', type: 'post', data: data, dataType: 'json', contentType: "application/json; charset=utf-8", async: false, success: function (result) { var displayname = $("#DisplayName").get(0); if (result == true) {
$("#divErr").append("Duplicate Display Name!<br/>"); } }, error: function (err) { alert(err.status + " - " + err.statusText); } }); }
这段代码借助$.ajax()
调用IsDuplicateDisplayName()
动作方法。注意displayname
参数是如何作为 JSON 对象传递的。如果数据库中已经存在一个显示名称,success
方法将接收一个值true
。在这种情况下,使用append()
方法将一条错误消息附加到 divErr。
虽然这里没有讨论,但是检查重复的电子邮件的工作原理是相似的。
比较密码和确认密码字段
确保在密码和确认密码文本框中输入的值相同很简单。清单 5-31 展示了它是如何完成的。
***清单 5-31。*验证密码和确认密码
if ($("#Password").val() != $("#ConfirmPassword").val()) { $("#divErr").append("Password mismatch!<br/>"); }
对文本框中输入的值进行比较,如果有任何不匹配,将向divErr
元素添加一条错误消息。
处理无效事件并显示验证错误
要显示来自输入类型的其他验证错误(required
字段、min
/ max
溢出、电子邮件和 URL 模式等等),需要处理所有<input>
元素上的invalid
事件。清单 5-32 显示了这是如何完成的。
***清单 5-32。*处理invalid
事件
`KaTeX parse error: Expected '}', got 'EOF' at end of input: …unction () { (“input”).bind(“invalid”,OnInvalid);
…
})
function OnInvalid(evt) {
var input = evt.target;
var validity = input.validity;
if (!validity.valid) {
if (input.id == “DisplayName”)
{ $(“#divErr”).append(“Please enter Display Name!
”); }
if (input.id == “Email”)
{ $(“#divErr”).append(“Please enter Email Address!
”); }
if (input.id == “Password”)
{ $(“#divErr”).append(“Please enter Password!
”); }
if (input.id == “ConfirmPassword”)
{ KaTeX parse error: Expected 'EOF', got '#' at position 3: ("#̲divErr").append…(“#divErr”).append(“Please specify age!
”);
}
if (validity.rangeUnderflow) {
KaTeX parse error: Expected 'EOF', got '#' at position 3: ("#̲divErr").append…(“#divErr”).append(“Age must be between 18 and 100!
”);
}
}
}
evt.preventDefault();
}`
清单 5-32 中的代码将OnInvalid()
函数绑定为invalid
事件的事件处理程序。仔细看看OnInvalid()
功能。它使用了ValidityState
对象。首先,代码使用valid
属性检查控件是否包含有效值。如果控件包含无效值,则检查其id
属性,并在divErr
中追加一条错误消息。注意法定年龄的验证:它使用了valueMissing
、rangeUnderflow
和rangeOverflow
属性。
这就完成了用户注册页面。图 5-27 显示了页面如何响应 HTML5 验证。
**图 5-27。**由invalid
事件处理程序显示的 HTML5 验证
图 5-28 显示了服务器端错误的验证错误是如何显示的。
***图 5-28。*来自数据注释和 MVC 的服务器端验证
图 5-28 中所示的错误信息来自User
数据模型类。因为这种验证发生在服务器上,所以密码字段失去了它的值。
总结
大多数 ASP.NET 应用都是数据驱动的,并以某种方式涉及表单。HTML5 提供了一系列增强表单的特性,最引人注目的是新的输入类型。诸如email
、url
、tel
和date
之类的输入类型允许您在不需要任何客户端脚本的情况下对输入的数据进行验证。此外,必填字段、模式匹配、自动聚焦、占位符和拼写检查等功能可以帮助您用最少的标记和脚本开发丰富的表单。
当然,您可能仍然需要依赖服务器端验证或自定义验证作为后备机制来处理不支持 HTML5 的浏览器。HTML5 还允许您定制验证消息以及这些消息的显示方式。
Web 表单和 MVC 应用都可以使用 HTML5 表单功能。TextBox
服务器控件的TextMode
属性支持各种 HTML5 输入类型。MVC 应用可以使用原始的<input>
标签或者使用 HTML 助手来呈现 HTML5 输入类型。