ASP.NET AJAX的脚本控件,连接了服务器端和客户端,因为我们(可以)只在服务器端编程,而效果产生在客户端,这就需要我们首先在服务器端编写一个控件类,然后包含一个或几个脚本文件,其中定义了客户端组件,可以让开发人员只在服务端操作控件,而在页面上添加客户端行为
一个典型的脚本控件就是UpdateProgress,我们来看一下它的实现方式
创建一个aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UpdateProgress.aspx.cs" Inherits="Demo13_UpdateProgress" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<%= DateTime.Now %><br />
<asp:Button ID="Button1" runat="server" Text="Update" onclick="Button1_Click" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="1000">
<ProgressTemplate>
Loading......
</ProgressTemplate>
</asp:UpdateProgress>
</form>
</body>
</html>
在Button1的点击事件里,让线程等待三秒钟
protected void Button1_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000);
}
开发页面,我们点击按钮Button1,可以看到,等待一秒钟后,出现“Loading…”字样,因为我们设置了UpdateProfress的DisplayAfter为1000,这里代码1000毫秒,而我们让控件的点击事件触发,引发异步回送后,在服务器端停留了三秒钟,所以三秒后,时间更新,同时“Loading…”字样消失
我们打开在网页中右键选择打开源文件,可在页面的form结束之前找到如下代码
Sys.Application.add_init(function() {
$create(Sys.UI._UpdateProgress, {"associatedUpdatePanelId":null,"displayAfter":1000,"dynamicLayout":true}, null, null, $get("UpdateProgress1"));
});
这段代码,是不是很熟悉呢?没错,如果看过我上一节的文章的,就会很熟悉这种代码格式,它响应了Application的init事件,然后创建一个Sys.UI._UpdateProgress类型的组件,然后设置它绑定的ID,这里是Null,和displayAfter,停留多少毫秒后显示,和UpdateProgress的占位方式,最后,设置的是它要修饰的element
于是出现了IScriptControl接口
由于大部分的脚本控件对于以上两个方法实现相同,因此在开发时候,也可以直接继承ScriptControl类,它已经实现了IScriptControl接口
创建一个名为StyledTextBox.js的文件
/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace('Demo');//注册命名控件
Demo.StyledTextBox = function(element) {
Demo.StyledTextBox.initializeBase(this, [element]);//调用父类构造函数
this._highlightCssClass = null;
this._nohighlightCssClass = null;
this._onfocusHandler = null;
this._onblurHandler = null;
}
Demo.StyledTextBox.prototype =
{
//highlightCssClass属性
get_highlightCssClass: function() {
return this._highlightCssClass;
},
set_highlightCssClass: function(value) {
if (this._highlightCssClass !== value) {
this._highlightCssClass = value;
this.raisePropertyChanged('highlightCssClass');
}
},
//nohighlightCssClass属性
get_nohighlightCssClass: function() {
return this._nohighlightCssClass;
},
set_nohighlightCssClass: function(value) {
if (this._nohighlightCssClass !== value) {
this._nohighlightCssClass = value;
this.raisePropertyChanged('nohighlightCssClass');
}
},
initialize: function() {
Demo.StyledTextBox.callBaseMethod(this, 'initialize');
//创建两个EventHandler
this._onfocusHandler = Function.createDelegate(this, this._onFocus);
this._onblurHandler = Function.createDelegate(this, this._onBlur);
//把这两个EventHandler加到我们要修饰的控件上
$addHandlers(this.get_element(),
{ 'focus': this._onFocus, 'blur': this._onBlur },
this);
this.get_element().className = this._nohighlightCssClass;
},
dispose: function() {
$clearHandlers(this.get_element());
Demo.StyledTextBox.callBaseMethod(this, 'dispose');
},
//如果获得焦点,把highlightCssClass给到这个element的class上
_onFocus: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
},
//如果失去焦点,把nohighlightCssClass给到这个element的class上
_onBlur: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._nohighlightCssClass;
}
}
}
Demo.StyledTextBox.registerClass('Demo.StyledTextBox', Sys.UI.Control);
然后创建一个aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ScriptControl.aspx.cs" Inherits="Demo13_ScriptControl" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
.NoHighLight
{
border:solid 1px gray;
background-color:#EEEEEE;
}
.HighLight
{
border:solid 1px gray;
background-color:Ivory;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Demo13/StyledTextBox.js" />
</Scripts>
</asp:ScriptManager>
<input type="text" id="textBox" />
<script language="javascript" type="text/javascript">
Sys.Application.add_init(function() {
$create(
Demo.StyledTextBox,
{
highlightCssClass: "HighLight",
nohighlightCssClass: "NoHighLight"
},
null,
null,
$get("textBox"));
});
</script>
</form>
</body>
</html>
代码很简单,和上一讲的如出一辙。。。什么如出一辙,本来就是一回事,文本框获得焦点,样式设置为HighLight,失去焦点,样式设置为NoHighLight。
这里,我们还是在客户端进行编程的,还没有做到在服务端编写在客户端生效的这样一个效果
我们开始做一个服务端控件
创建一个名为StyledTextBox.cs的类
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;
namespace Demo
{
/// <summary>
///StyledTextBox 的摘要说明
/// </summary>
//继承自TextBox,实现接口IScriptControl
public class StyledTextBox : TextBox, IScriptControl
{
//两个属性,分别是控件或者焦点和失去焦点时候要设置的样式
public string HighlightCssClass { get; set; }
public string NoHighlightCssClass { get; set; }
#region IScriptControl 成员
//告诉ScriptManager将如何生成脚本代码
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Demo.StyledTextBox", this.ClientID);//参数1:创建的组件类型,参数2:返回此控件在客户端生成的ID
//添加两个属性
descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);
yield return descriptor;
}
//告诉页面我们要引入的脚本文件
public IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = this.ResolveClientUrl("~/Demo13/StyledTextBox.js");//ResolveClientUrl方法用于浏览器的指定资源的完全限定 URL
yield return reference;
}
#endregion
//以下是开发一个脚本控件,需要重写Control的两个方法
protected override void OnPreRender(EventArgs e)
{
//如果不在设计期间
if (!this.DesignMode)
{
//把自身注册给ScriptManager的ScriptControl
ScriptManager.GetCurrent(this.Page).RegisterScriptControl<StyledTextBox>(this);
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
{
//把自身注册给ScriptManager的ScriptDescriptors
ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
}
base.Render(writer);
}
}
}
然后修改刚才的aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ScriptControl.aspx.cs" Inherits="Demo13_ScriptControl" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ Register Namespace="Demo" TagPrefix="demo" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
.NoHighLight
{
border:solid 1px gray;
background-color:#EEEEEE;
}
.HighLight
{
border:solid 1px gray;
background-color:Ivory;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<input type="text" id="textBox" />
<asp:ScriptManager runat="server" ID="s"></asp:ScriptManager>
<demo:StyledTextBox ID="StyledTextBox1" runat="server" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
</form>
</body>
</html>
注意,这里我们不需要javascript代码,也不需要在页面中引入我们刚才的js文件,只需要在页面中注册这个脚本控件,然后在页面中当作服务端控件那样直接使用,设置属性就可以啦
我们看到StyledTextBox继承了TextBox,同时扩展了TextBox,这个概念和客户端组件的Control模型很相似,事实上普通的脚本控件包含的脚本中大多数都是定义了客户端的Control模型的组件
和客户端的Behavior模型概念类似的服务端模型是Extender模型,可以为一个服务器端控件附加多个Extender,Extender模型理论上继承自IExtenderControl即可,实际上开发时候议案继承自ExtenderControl类,免去一些额外的工作
新建一个类库项目,添加引用System.Web和System.Web.Extensions
创建一个名为FocusBehavior.js的文件
Type.registerNamespace('Demo');
Demo.FocusBehavior = function(element) {
Demo.FocusBehavior.initializeBase(this, [element]);
this._highlightCssClass = null;
this._nohighlightCssClass = null;
}
Demo.FocusBehavior.prototype =
{
get_highlightCssClass: function() {
return this._highlightCssClass;
},
set_highlightCssClass: function(value) {
if (this._highlightCssClass !== value) {
this._highlightCssClass = value;
this.raisePropertyChanged('highlightCssClass');
}
},
get_nohighlightCssClass: function() {
return this._nohighlightCssClass;
},
set_nohighlightCssClass: function(value) {
if (this._nohighlightCssClass !== value) {
this._nohighlightCssClass = value;
this.raisePropertyChanged('nohighlightCssClass');
}
},
initialize: function() {
Demo.FocusBehavior.callBaseMethod(this, 'initialize');
$addHandlers(this.get_element(),
{ 'focus': this._onFocus, 'blur': this._onBlur },
this);
this.get_element().className = this._nohighlightCssClass;
},
dispose: function() {
$clearHandlers(this.get_element());
Demo.FocusBehavior.callBaseMethod(this, 'dispose');
},
_onFocus: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
},
_onBlur: function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._nohighlightCssClass;
}
}
}
Demo.FocusBehavior.registerClass('Demo.FocusBehavior', Sys.UI.Behavior);
代码和之前的一样,我就没添加注释,看过我之前的文章,看这段代码不是问题
然后创建一个名为FocusExtender.cs的类文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
//描述我们需要引用的资源
[assembly: WebResource("FocusExtender.FocusBehavior.js", "application/x-javascript")]
namespace FocusExtender
{
[TargetControlType(typeof(Control))]
public class FocusExtender : ExtenderControl
{
public string HighlightCssClass { get; set; }
public string NoHighlightCssClass { get; set; }
protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
{
ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("Demo.FocusBehavior", targetControl.ClientID);
descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);
yield return descriptor;
}
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Assembly = "FocusExtender";
reference.Name = "FocusExtender.FocusBehavior.js";
yield return reference;
}
}
}
在这里描述应用资源的时候应该注意,这里不是文件名,也不是这个类库的名称加点然后加文件名
我们点击项目右键属性,打开属性页面
我们的资源名称,是默认命名控件.文件名称
这里的代码,与前面的示例唯一不同的是,多了一个targetControl,在类名前加一个标识,表示我们这个控件作用到那种类型的控件上,我们这里设置为“Control”,表示所有控件
还应该注意一点,我们应该在项目生成操作的时候,把js文件作为嵌入的资源,点击js文件属性,然后在属性对话框里做相应修改
然后我们就可以在我们的网站里使用它啦
在网站中点击右键添加引用,选择我们创建的FocusExtender项目,会在bin目录下出现一个FocusExtender.dll,注意要先生成一下
创建aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FocusExtender.aspx.cs" Inherits="Demo13_FocusExtender" %>
<%@ Register Assembly="FocusExtender" Namespace="FocusExtender" TagPrefix="demo" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
.NoHighLight
{
border:solid 1px gray;
background-color:#EEEEEE;
}
.HighLight
{
border:solid 1px gray;
background-color:Ivory;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:TextBox runat="server" ID="textBox" />
<demo:FocusExtender ID="FocusExtender1" runat="server" TargetControlID="textBox" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
<asp:Panel Width="200" Height="200" ID="panel" runat="server">Hello World!</asp:Panel>
<demo:FocusExtender ID="FocusExtender2" runat="server" TargetControlID="panel" HighlightCssClass="HighLight" NoHighlightCssClass="NoHighLight" />
</form>
</body>
</html>
这样,我们把我们创建的控件“附加”到了一个文本框和一个Panel上,在同时我们提供了三个属性,作用的控件,和两个样式属性,运行页面,得到与前面我们的脚本控件相同的效果
在异步刷新中,由于不刷新整个页面,因此可以保存在页面变量中,但是完整的PostBack需要将状态从客户端提交到服务器端,然后再写回给客户端,客户端向服务器端提交信息的方法有以下三种
那么,如果我们要保存页面的某个状态,就分两种情况啦
一种是异步刷新,因为异步刷新的时候,页面并没有销毁,所以,我们可以把保存这种状态的键值放在window对象或者一个HiddenField中,但是如果是传统的更新,页面是会被销毁的,则只能保存在HiddenField中啦
为了让UpdatePanle可以使用内联脚本,就需要使用一个内联脚本控件
创建一个aspx页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="InlineScripts.aspx.cs" Inherits="Demo13_InlineScripts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ID="sm" />
<asp:UpdatePanel runat="server" ID="update">
<ContentTemplate>
<%= DateTime.Now %>
<asp:Button runat="server" ID="btnRefresh" Text="Refresh" />
<script language="javascript" type="text/javascript">
alert("Xiaoyaojian");
</script>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
打开页面,刷新页面,都会弹出提示框,而在我们点击Refresh后,脚本却并没有被执行,这不是我们想要的效果,但是这里的脚本在异步回送的时候确实是被加载啦,那要怎么做呢 。。。。。
我们创建一个名为InlineScript的类库项目,添加一个类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.IO;
namespace InlineScript
{
public class InlineScript : Control
{
//重写Render方法,每次UpdatePanle更新,这个方法都会被调用
protected override void Render(HtmlTextWriter writer)
{
ScriptManager sm = ScriptManager.GetCurrent(this.Page);
if (sm.IsInAsyncPostBack)//如果页面是异步更新的情况下
{
StringBuilder sb = new StringBuilder();
base.Render(new HtmlTextWriter(new StringWriter(sb)));
//得到的UpdatePanel中的script标签的所有内容
string script = sb.ToString();
ScriptManager.RegisterStartupScript(this, typeof(InlineScript), this.UniqueID, script, false);//把这段脚本注册到页面上
}
else
{
base.Render(writer);
}
}
}
}
生成项目,然后和上面一样,在网站项目中添加对这个项目的引用,然后修改上面的页面
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="InlineScripts.aspx.cs" Inherits="Demo13_InlineScripts" %>
<%@ Register Assembly="InlineScript" Namespace="InlineScript" TagPrefix="demo" %><%--注册这个控件--%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ID="sm" />
<asp:UpdatePanel runat="server" ID="update">
<ContentTemplate>
<%= DateTime.Now %>
<asp:Button runat="server" ID="btnRefresh" Text="Refresh" />
<%--使用注册的控件--%>
<demo:InlineScript runat="server">
<script language="javascript" type="text/javascript">
alert("Xiaoyaojian");
</script>
</demo:InlineScript>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
打开页面,刷新,点击按钮,都会弹出提示框,对嘛 这才是我们要的效果