渐层光棒
不喜欢GridView控件单调的Header区、单调的选取光棒吗?这里有个小技巧可以让你的GridView控件看起来与众不同,请先准备两张图形。
图4-8-50(4-71.tif)
这种渐层图形可以用Photoshop或PhotoImpact轻易做出来,接着将这两个图形文件加到项目的Images目录中,左边取名为titlebar.gif、右边取名为gridselback.gif,然后开启一个新网页,组态SqlDataSource控件连结到任一数据表,再加入GridView控件系结至此SqlDataSource控件,接着将Enable Selection打勾,切换至网页Source页面,加入CSS的程序代码。
程序4-8-10
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="GrandientSelGrid.aspx.cs" Inherits="GrandientSelGrid" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <style type="text/css"> .grid_sel_back { background-image:url(Images/gridselback.gif); background-repeat:repeat-x } .title_bar { background-image:url(Images/titlebar.gif); background-repeat:repeat-x } </style> |
完成后切回设计页面,设定GridView控件的SelectedRowStyle及HeaderStyle属性。
图4-8-51(4-72.tif)
完成后执行网页,你会见到很不一样的GridView。
图4-8-52(4-73.tif)
2 Footer or 2 Header
前面曾提及,GridView控件并没有限制我们只能在里面加入一个Footer,因此我们可以透过程序4-8-11的方式,添加另一个Footer至GridView控件中。
程序4-8-11
protected void GridView1_PreRender(object sender, EventArgs e) { //if no-data in datasource,GridView will not create ChildTable. if (GridView1.Controls.Count > 0 && GridView1.Controls[0].Controls.Count > 1) { GridViewRow row2 = new GridViewRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal); TableCell cell = new TableCell(); cell.Text = "Footer 2"; cell.Attributes["colspan"] = GridView1.Columns.Count.ToString(); //merge columns row2.Controls.Add(cell); GridView1.Controls[0].Controls.AddAt(GridView1.Controls[0].Controls.Count - 1, row2); } } |
相同的,同样的方法也可以用于添加另一个Header至GridView控件中,这个范例看起来无用,但是却给了无限的想象空间,这是实现GridView Insert及Collapsed GridView功能的基础。
Group Header
想合并Header中的两个字段为一个吗?很简单!只要在RowCreated事件中将欲被合并的字段移除,将另一字段的colspan设为2即可。
程序4-8-12
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.Header) { e.Row.Cells.RemoveAt(3); e.Row.Cells[2].Attributes["colspan"] = "2"; e.Row.Cells[2].Text = "Contact Information"; } } |
图4-8-53为执行画面。
图4-8-53(4-74.tif)
我想,应该不需要我再解释2这个数字从何而来了吧。 ^_^
Group Row
想将同值的字段合成一个吗?4-8-13的程序代码可以帮你达成。
程序4-8-13
private void PrepareGroup() { int lastSupID = -1; GridViewRow currentRow = null; List<GridViewRow> tempModifyRows = new List<GridViewRow>(); foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { if (currentRow == null) { currentRow = row; int.TryParse(row.Cells[2].Text, out lastSupID); continue; } int currSupID = -1; if (int.TryParse(row.Cells[2].Text, out currSupID)) { if (lastSupID != currSupID) { currentRow.Cells[2].Attributes["rowspan"] = (tempModifyRows.Count+1).ToString(); currentRow.Cells[2].Attributes["valign"] = "center"; foreach (GridViewRow row2 in tempModifyRows) row2.Cells.RemoveAt(2); lastSupID = currSupID; tempModifyRows.Clear(); currentRow = row; lastSupID = currSupID; } else tempModifyRows.Add(row); } } } if (tempModifyRows.Count > 0) { currentRow.Cells[2].Attributes["rowspan"] = (tempModifyRows.Count + 1).ToString(); currentRow.Cells[2].Attributes["valign"] = "center"; foreach (GridViewRow row2 in tempModifyRows) row2.Cells.RemoveAt(2); } } protected void GridView1_PreRender(object sender, EventArgs e) { PrepareGroup(); } |
这段程序代码应用了先前所提过的GridViewRow控件及TableCell的使用方式,图4-8-54为执行结果。
图4-8-54
Master-Detail GridView
Master-Detail,也就是主明细表的显示,是数据库应用常见的功能,运用DataSource及GridView控件可以轻易做到这点,请建立一个网页,加入两个GridView控件,一名为GridView1,用于显示主表,二名为GridView2,用于显示明细表,接着加入两个SqlDataSource控件,一个连结至Northwind数据库的Orders数据表,另一个连结至Order Details资料表,于连结至Order Details资料表的SqlDataSource中添加WHERE条件来比对OrderID字段,值来源设成GridView1的SelectedValue属性。
图4-8-61
接下来请将GridView1的DataSoruce设为Orders的SqlDataSource,GridView2的DataSource设为Order Details的SqlDataSource,最后将GridView1的Enable Selection打勾即可完成Master-Detail的范例。
图4-8-62
那这是如何办到的呢?当使用者点选GridView1上某笔数据的Select连结时,GridView1的SelectedValue属性便会设成该笔数据的DataKeyName属性所指定的字段值,而连结至Order Details的SqlDataSource又以该属性做为比对OrderID字段时的值来源,结果便成了,使用者点选了Select连结,PostBack发生,GridView2向连结至Order Details的SqlDataSource索取资料,该SqlDataSource以GridView1.SelectedValue做为比对OrderID字段的值,执行选取数据的SQL指令后,该结果集便是GridView1所选取那笔数据的明细了。
Master-Detail GridView Part 2
前面的Master-Detail GridView控件应用,相信你已在市面上的书、或网络上见过,但此节中的GridView控件应用包你没看过,但一定想过!请见图4-8-63。
图4-8-63
图4-8-64
你一定很想惊呼?这是GridView吗??不是第三方控件的效果吧?是的!这是GridView控件,而且只需要不到100行程序代码!!请先建立一个UserControl:DetailsGrid.ascx,加入一个SqlDataSource控件连结至Northwind的Order Details数据表,选取所有字段,接着在WHERE区设定如图4-8-65的条件。
图4-8-65
接着加入一个GridView控件系结至此SqlDataSource控件,并将Enable Editing打勾,然后于原始码中键入4-8-17的程序代码。
程序4-8-17
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class DetailsGrid : System.Web.UI.UserControl { public int OrderID { get { object o = ViewState["OrderID"]; return o == null ? -1 : (int)o; } set { ViewState["OrderID"] = value; SqlDataSource1.SelectParameters[0].DefaultValue = value.ToString(); } } protected void Page_Load(object sender, EventArgs e) { } } |
接着建立一个新网页,加入SqlDataSource控件系结至Northwind的Orders数据表,然后加入一个GridView控件,并于其字段编辑器中加入一个TemplateField,于其内加入一个LinkButton控件,设定其属性如图4-8-66。
图4-8-66
然后设定LinkButton的DataBindings如图4-8-67。
图4-8-67
然后于原始码中键入4-8-18的程序代码。
程序4-8-18
using System; using System.Collections.Generic; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class CollapseGridView : System.Web.UI.Page { private List<int> _collaspedRows = new List<int>(); private List<GridViewRow> _delayAddRows = new List<GridViewRow>(); private bool RowIsCollasped(GridViewRow row) { if(_collaspedRows.Count > 0) return _collaspedRows.Contains((int)GridView1.DataKeys[row.RowIndex].Value); return false; } private void CreateDetailRow(GridViewRow gridRow) { if (RowIsCollasped(gridRow)) { GridViewRow row = new GridViewRow(gridRow.RowIndex, -1, DataControlRowType.DataRow, DataControlRowState.Normal); TableCell cell = new TableCell(); row.Cells.Add(cell); TableCell cell2 = new TableCell(); cell2.Attributes["colspan"] = (GridView1.Columns.Count - 1).ToString(); Control c = LoadControl("DetailsGrid.ascx"); ((DetailsGrid)c).OrderID = (int)GridView1.DataKeys[gridRow.RowIndex].Value; cell2.Controls.Add(c); row.Cells.Add(cell2); _delayAddRows.Add(row); } } protected void Page_Load(object sender, EventArgs e) { } protected override void LoadViewState(object savedState) { Pair state = (Pair)savedState; base.LoadViewState(state.First); _collaspedRows = (List<int>)state.Second; } protected override object SaveViewState() { Pair state = new Pair(base.SaveViewState(), _collaspedRows); return state; } } |
接下来在TemplateField中的LinkButton的Click事件中键入4-8-19的程序代码。
程序4-8-19
protected void LinkButton1_Click(object sender, EventArgs e) { LinkButton btn = (LinkButton)sender; int key = int.Parse(btn.CommandArgument); if (_collaspedRows.Contains(key)) { _collaspedRows.Remove(key); GridView1.DataBind(); } else { _collaspedRows.Clear(); // clear. _collaspedRows.Add(key); GridView1.DataBind(); } } |
最后在GridView控件的RowCreated、PageIndexChanging事件中键入4-8-20的程序代码。
程序4-8-20
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e) { if(e.Row.RowType == DataControlRowType.DataRow) CreateDetailRow(e.Row); else if (e.Row.RowType == DataControlRowType.Pager && _delayAddRows.Count > 0) { for (int i = 0; i < GridView1.Rows.Count; i++) { if (RowIsCollasped(GridView1.Rows[i])) { GridView1.Controls[0].Controls.AddAt(GridView1.Rows[i].RowIndex + 2, _delayAddRows[0]); _delayAddRows.RemoveAt(0); } } } } protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e) { _collaspedRows.Clear(); } |
执行后你就能看到前图的效果了,那具体是如何做到的呢?从前面的说明,我们知道了可以在GridView控件中动态的插入一个 GridViewRow控件,而GridViewRow控件可以拥有多个Cell,每个Cell可以拥有子控件,那么当这个子控件是一个UserControl呢 ?相信说到这份上,读者已经知道整个程序的运行基础及概念了,剩下的细节如LoadViewState、SaveViewState皆已在前面章节提过,看懂这个范例后!你应该也想到了其它的应用了(UserControl中放DetailsView、FormView、MultiView,哈!),对于GridView!你已经毫无疑问了!
4-8-4、GridView的效能
OK,GridView控件功能很强大,但是如果你仔细思考下GridView控件的分页是如何做的,会发现她的做法其实隐含着一个很大的效能问题,GridView控件在分页功能启动的情况下,会建立一个PageDataSource对象,由这个对象负责向DataSource索取数据,于索取数据时一并传入DataSourceSelectArgument对象,此对象中便包含了起始的列及需要的列数,看起来似乎没啥问题吗?其实不然,当DataSource控件不支持分页时,PageDataSource对象只能以该DataSource所传回的数据来做分页,简略的说!SqlDataSource控件是不支持分页的,这时PageDataSource会要求SqlDataSource控件传回资料,而SqlDataSource控件就用SelectQuery中的SQL指令向数据库要求数据,结果便是,当该SQL指令选取100000笔数据时,SqlDataSource所传回给PageDataSource的资料也是100000笔!!这意味着,GridView每次做数据系结显示时,是用100000笔资料在分页,不管显示的是几笔,存在于内存中的都是100000笔!如果同时有10个人、100个人在使用此网页,可想而知Server的负担有多重了,即使有Cache加持,一样会有100000笔数据在内存中!以往在ASP.NET 1.1时,可以运用DataGrid控件的CustomPaging功能来解决此问题,但GridView控件并未提供这个功能,我们该怎么处理这个问题呢?在提出解决方案前,我们先谈谈GridView控件为何将这么有用的功能移除了?答案很简单,这个功能已经被移往DataSource控件了,这是因为DataSource控件所需服务的不只是GridView,FormView、DetailsView都需要她,而且她们都支持分页,如果将CustomPaging直接做在这些控件上,除了控件必须有着重复的程序代码外,设计师于撰写分页程序时,也需针对不同的控件来处理,将这些移往DataSource控件后,便只会有一份程序代码。说来好听,那明摆着SqlDataSource控件就不支持分页了,那该如何解决这个问题了,答案是ObjectDataSource,这是一个支持分页的DataSource控件,只要设定几个属性及对数据提供者做适当的修改后,便可以达到手动分页的效果了。请建立一个WebiSte项目,添加一个DataSet连结到Northwind的Customers资料表,接着新增一个Class,档名为NorthwindCustomersTableAdapter.cs,键入4-8-26的程序代码。
程序4-8-26
using System; using System.ComponentModel; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; namespace NorthwindTableAdapters { public partial class CustomersTableAdapter { [System.ComponentModel.DataObjectMethodAttribute( System.ComponentModel.DataObjectMethodType.Select, true)] public virtual Northwind.CustomersDataTable GetData(int startRowIndex, int maximumRows) { this.Adapter.SelectCommand = new System.Data.SqlClient.SqlCommand("SELECT {COLUMNS} FROM " + "(SELECT {COLUMNS},ROW_NUMBER() OVER(ORDER BY {SORT}) As RowNumber FROM {TABLE} {WHERE}) {TABLE} " + "WHERE RowNumber > {START} AND RowNumber < {FETCH_SIZE}",Connection); this.Adapter.SelectCommand.CommandText = this.Adapter.SelectCommand.CommandText.Replace("{COLUMNS}", "*"); this.Adapter.SelectCommand.CommandText = this.Adapter.SelectCommand.CommandText.Replace("{TABLE}", "Customers"); this.Adapter.SelectCommand.CommandText = this.Adapter.SelectCommand.CommandText.Replace("{SORT}", "CustomerID"); this.Adapter.SelectCommand.CommandText = this.Adapter.SelectCommand.CommandText.Replace("{WHERE}", ""); this.Adapter.SelectCommand.CommandText = this.Adapter.SelectCommand.CommandText.Replace("{START}", startRowIndex.ToString()); this.Adapter.SelectCommand.CommandText = this.Adapter.SelectCommand.CommandText.Replace("{FETCH_SIZE}", (startRowIndex+maximumRows).ToString()); Northwind.CustomersDataTable dataTable = new Northwind.CustomersDataTable(); this.Adapter.Fill(dataTable); return dataTable; } public virtual int GetCount(int startRowIndex, int maximumRows) { SqlCommand cmd = new System.Data.SqlClient.SqlCommand("SELECT COUNT(*) AS TOTAL_COUNT FROM Customers", Connection); Connection.Open(); try { return (int)cmd.ExecuteScalar(); } finally { Connection.Close(); } } } } |
此程序提供了两个函式,GetData函式需要两个参数,一个是起始的笔数,一个是选取的笔数,利用这两个参数加上SQL Server 2005新增的RowNumber,便可以向数据库要求传回特定范围的数据。那这两个参数从何传入的呢?当GridView向ObjectDataSource索取数据时,便会传入这两个参数,例如当GridView的PageSize是10时,第1页时传入的startRowIndex便是10,maximumRows就是10,以此类推。第二个函式是GetCount,对于GridView来说,她必须知道系结数据的总页数才能显示Pager区,而此总页数必须由资料总笔数算出,此时GridView会向PageDataSource要求资料的总笔数,而PageDataSource在DataSource控件支持分页的情况下,会要求其提供总笔数,这时此函式就会被呼叫了。大致了解这个程序后,回到设计页面,加入一个ObjectDataSource控件,于SELECT页次选取带startRowIndex及maximumRows参数的函式。
图4-8-68
按下Next按纽后,精灵会要求我们设定参数值来源,请直接按下Finish来完成组态。
图4-8-69
接着设定ObjectDataSource的EnablePaging属性为True,SelectCountMethod属性为GetCount(对应了程序4-8-26中的GetCount函式),最后放入一个GridView控件,系结至此ObjectDataSource后将Enable Paging打勾,就完成了手动分页的GridView范例了。
图4-8-70
在效能比上,手动分页的效能在数据量少时绝对比Cache来的慢,但数据量大时,手动分页的效能及内存耗费就一定比Cache来的好。