ASP.NET 2.0 GridView

渐层光棒

不喜欢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">

<styletype="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-4GridView的效能

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来的好。