1. 前言
前些天在http://www.ajaxmatters.com上找到一篇关于不使用UpdatePanel来实现Gridview相关ajax操作的文章,回家按部就班地做了一个实验,感觉不错,里面涉及到的编程技术比我在国内网站上查到的要更全面一些,特此翻译全文,以飨读者。
2. 全文翻译
【译者注: 文章来自www.ajaxmatters.com论坛】
【上篇】
本文详细描述了如何使用.net的ICallbackEventHandler接口开发包括排序(sorting), 分页显示(paging), 改变页长(page length change)AJAX Gridview控件, 我在下面会介绍到具体编程,读者也可以在文章最后下载到所有的源码。
本例开发的Gridview控件的基本功能如下(所有操作都是异步的):
- 点击列名旁边的箭头升序或者降序排列数据
- 翻页
- 改变每页显示的数目
在本例中,我们将会用到一个ASP.NET中最为强大的特性 - RenderControl。该方法能使我们方便地(在服务器端 - 译者注)通过HtmlTextWriter和StirngWriter对象访问到一个控件的HTML代码。
示例:
using (StringWriter sw = new StringWriter())

...{

HtmlTextWriter htw = new HtmlTextWriter(sw);

_grid.RenderControl(htw);

htw.Flush();

string result = sw.ToString();

}
我们获取到grid控件的html格式的代码并赋给一个string变量 - 这个工作是在绑定控件数据源之后做的。
现在,我们从开发UI代码开始一步一步完成这个示例程序:
首先,在网页的<form>标签内写下如下代码,创建一个Gridview和Dropdownlist控件:
<div id="Gridview" >
<asp:GridView EnableViewState="false" runat="server" id="_grid" OnRowDataBound="_grid_RowDataBound" AllowPaging="True" >
</asp:GridView>

<br />

</div> Change page length to --

<asp:DropDownList ID="ddl" runat="server">

</asp:DropDownList>
注意Gridview控件的RowDataBound事件已经激活了。
下一步,我们来创建一个DataTable, 作为Gridview控件的数据源:
public DataTable _sampleData

...{


get ...{

DataTable dt = (DataTable)ViewState["DataTable"];

if(dt == null)


...{

dt = new DataTable();

dt.Columns.Add(new DataColumn("Contact Name",typeof(string)));

dt.Columns.Add(new DataColumn("Company Name", typeof(string)));

dt.Columns.Add(new DataColumn("City", typeof(string)));

dt.Columns.Add(new DataColumn("Country", typeof(string)));



dt.Rows.Add(new object[] ...{ "Maria Anders" ,"Alfreds Futterkiste","Berlin","Germany"});


dt.Rows.Add(new object[] ...{ "Ana Trujillo" ,"Emparedados y helados ","México D.F.","Mexico"});


dt.Rows.Add(new object[] ...{ "Antonio Moreno", "Antonio Moreno Taquería", "México D.F.","Mexico" });

ViewState["DataTable"] = dt;

}

return dt;

}
我们使用ViewState 而不是Session 变量来存放DataTable.
比如, ViewState["DataTable"] = dt;
用session可以缓存整个网站的变量, ViewState就只存某个页面的。在绑定数据的时候,我们只需要写_grid.DataSource = _sampleData即可。
下面是Gridview的4个操作方法:
private void sortGrid(string Argument , string pageLength)

...{

DataView dv = _sampleData.DefaultView;

result = "";

dv.Sort = Argument;

_grid.DataSource = dv;

_grid.PageSize = Convert.ToInt16(pageLength);

_grid.DataBind();

renderGrid(_grid);

}
private void changePage(string Argument , string pageLength)

...{
result = "";

_grid.DataSource = _sampleData;

_grid.PageSize = Convert.ToInt16(pageLength);

_grid.PageIndex = Convert.ToInt16(Argument);

_grid.DataBind();

renderGrid(_grid);

}
private void changePageLength(string Argument, string pageLength)

...{

result = "";

_grid.DataSource = _sampleData;

_grid.PageSize = Convert.ToInt16(Argument);

_grid.DataBind();

renderGrid(_grid);

//pageLength is not used

}
private void renderGrid(GridView _grid)

...{
using (StringWriter sw = new StringWriter())

...{

HtmlTextWriter htw = new HtmlTextWriter(sw);

_grid.RenderControl(htw);

htw.Flush();

result = sw.ToString();

}
}
上述4个方法的名称显示了其实现的功能,而前三个的第二个参数'pageLength'是dropdownlist控件选定的值。
我们需要另外4个方法来实现ICallbackEventHandler异步回调 - 两个是javascript方法(其一对服务器端回调,其二用来接收服务器端返回的值并做异步刷新), 两个是服务器端的方法( RaiseCallbackEvent 和GetCallbackResult , 译者注:ICallbackEventHandler的两个接口方法,前面的用来响应客户端回调,后面的用来返回操作结果)。
JavaScript方法如下:
function UpdateGrid(args)

...{

args = args + "$" + window.document.getElementById('ddl').value;

<%= ClientScript.GetCallbackEventReference(this,"args", "ShowResult", null) %>

}
在页面装载完成之后,我们将在页面的源代码里面看到上面的代码编程了这个样子:
function UpdateGrid(args)

...{

args = args + "$" + window.document.getElementById('ddl').value;

WebForm_DoCallback('__Page',args,ShowResult,null,null,false);

}
在回调的时候,我们会触发服务器端的一个事件,因此,UpdateGrid(args)方法需要放在<form>标签之内,否则的话会发生一个JavaScript错误。我们通过Page.ClientScript.RegisterClientScriptBlock方法来注册这个JavaScipt方法,这样的话它将仅出现在<form>标签之内。
function ShowResult(eventArgument,context)

...{
window.document.getElementById('Gridview').innerHTML = eventArgument;
}
这个方法将在服务器端响应客户端之后触发,它负责处理服务器端的返回值。在这里我们只是简单的用返回值替换掉innerHTML属性。innerHTML包含了需要更新的Gridview的HTML代码。
服务器端的代码如下:
public string GetCallbackResult()

...{
return result;
}
该方法返回在ShowResult客户端方法中设置innerHTML的结果。
public void RaiseCallbackEvent(string eventArgument)

...{
string[] args = eventArgument.Split('$');


if (args[0] == "sort") ...{ sortGrid(args[1], args[2]); }


else if (args[0] == "changePage") ...{ changePage(args[1], args[2]); }


else if (args[0] == "changePageLength") ...{ changePageLength(args[1], args[2]); }
}
该函数输入参数形如"changePage$1$10" 或者"sort$1$10" 或者 "changePageLength$1$10" 或者 "sort$Contact Name Asc" 或者 “sort$Contact Name Desc$10”, 我们用字符串解析函数得到下列参数列表:
- 操作名称(Action);
- Gridview控件的页码(Page Index of the Gridview);
- Dropdownlist的每页显示长度(Page size from the dropdownlist)
在RaiseCallbackEvent方法中我们调用了一些处理具体操作的函数,每个函数都会设置Gridview更新之后的HTML代码给返回参数。
页面装载代码:
if (!IsPostBack)

...{
_grid.DataSource = _sampleData;

_grid.DataBind();

ddl.Items.Add("10");

ddl.Items.Add("20");

ddl.Items.Add("30");

ddl.Attributes.Add("onchange", "javascript:UpdateGrid('changePageLength$' + this.value);");
}
这里设置Gridview控件每页的显示长度。
现在实现Gridview的RowDataBound事件,为了列排序功能,修改了Gridview控件的列头:
if (e.Row.RowType == DataControlRowType.Header)

...{

for (int i = 0; i < e.Row.Cells.Count; i++)

...{

e.Row.Cells[i].Text = string.Format("{0}<img alt="Ascending" src="Images/up.jpg" onclick="UpdateGrid('sort${0} Asc')"; /><img alt="Descending" src="Images/down.jpg" onclick="UpdateGrid('sort${0} desc') "; />", e.Row.Cells[i].Text);

}
}
列头上加上了上下箭头,点击他们的时候将调用UpdateGrid方法(在客户端 - 译者注)回调服务器端的升序和降序排列方法。
随后我们对页码行(Page Row)做一些绑定时的修改,使页码能够在点击的时候调用UpdateGrid方法。
else if (e.Row.RowType == DataControlRowType.Pager)

...{

GridView gdv = (GridView)sender;

int _pageCount = gdv.PageCount;

e.Row.Cells[0].Text = "";

for (int i = 0; i < _pageCount; i++)

...{
HyperLink hyp = new HyperLink();

hyp.Text = i.ToString() + " ";

hyp.Attributes.Add("href", "javascript:UpdateGrid('changePage$" + i + "');");

e.Row.Cells[0].Controls.Add(hyp);

Label l = new Label();

l.Text = " ";

e.Row.Cells[0].Controls.Add(l);

hyp = null;
}
}
_pageCount 用来保存Gridview控件的页数,为了显示页码,我们使用了HyperLink + Label +空格 的方式。
最后,把EnableViewState属性设置为false,用来减轻页面的负担。
【下篇】
上篇我们使用了ICallbackEventHandler接口实现了一个AJAX的Gridview控件,功能如下:
- 点击列名旁边的上下小箭头实现升序和降序排列的功能
- 翻页
- 改变每页显示长度
本篇将讨论如何通过双击数据格(grid cell)来修改格子里的数据,实现服务器端数据更新的时候来异步刷新页面的功能。关键点如下:
- 在Page Load的时候只绑定一次数据到Gridview控件
- 只有当更新数据要求传到服务器端的时候才更新Gridview控件
UI代码:
<div id="Gridview">
<asp:GridView EnableViewState="false" runat="server" id="_grid" OnRowDataBound="_grid_RowDataBound">
</asp:GridView>
<span id ="ServerMsg"></span>
</div>
<br />
<input type=button value="Update" onclick="javascript: JSUpdateTable ();" />

<script language="javascript">...
function UpdateGrid(args)

...{
<%= ClientScript.GetCallbackEventReference(this,"args", "ShowResult", null) %>;
}
</script>
使用一个DataTable作为数据源:
public DataTable _sampleData

...{
get

...{
DataTable dt = (DataTable) Session["DataTable"];
if(dt == null)

...{
dt = new DataTable();
dt.Columns.Add(new DataColumn("Contact Name",typeof(string)));
dt.Columns.Add(new DataColumn("Company Name", typeof(string)));
dt.Columns.Add(new DataColumn("City", typeof(string)));
dt.Columns.Add(new DataColumn("Country", typeof(string)));


dt.Rows.Add(new object[] ...{ "Maria Anders" ,"Alfreds Futterkiste","Berlin","Germany"});

dt.Rows.Add(new object[] ...{ "Ana Trujillo" ,"Emparedados y helados ","México D.F.","Mexico"});

dt.Rows.Add(new object[] ...{ "Antonio Moreno", "Antonio Moreno Taquería", "México D.F.","Mexico" });
Session["DataTable"] = dt;
}
return dt;
}
set

...{
Session["DataTable"] = value;
}
}
注意:在这里我们使用Session而不是ViewState存放DataTable变量,因为我们需要在服务器端更新数据,如果使用ViewState的话需要对页面进行回发(post back)才行。
使用下面的代码把Datatable绑定到Gridview控件: _grid.DataSource = _sampleData; 更新的时候: _sampleData = _tempTable, _tempTable是更新了数据之后的临时DataTable变量。
页面类继承ICallbackEventHandler接口:
public partial class Default: System.Web.UI.Page, ICallbackEventHandler
添加RaiseCallbackEvent方法和GetCallbackResult方法:
public void RaiseCallbackEvent(string eventArgument)

...{
string[] args = eventArgument.Split('$');
if (args[0] == "updateTable") updateTable(args[1]);
}
