ASP.NET 2.0 GridView

 

渐层光棒

 

 不喜欢GridView控件单调的Header区、单调的选取光棒吗?这里有个小技巧可以让你的GridView控件看起来与众不同,请先准备两张图形。

4-8-50(4-71.tif)

这种渐层图形可以用PhotoshopPhotoImpact轻易做出来,接着将这两个图形文件加到项目的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控件的SelectedRowStyleHeaderStyle属性。

4-8-51(4-72.tif)

完成后执行网页,你会见到很不一样的GridView

4-8-52(4-73.tif)

 

2 Footer or 2 Header

 

 前面曾提及,GridView控件并没有限制我们只能在里面加入一个Footer,因此我们可以透过程序4-8-11的方式,添加另一个FooterGridView控件中。

程序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);

      }

}

相同的,同样的方法也可以用于添加另一个HeaderGridView控件中,这个范例看起来无用,但是却给了无限的想象空间,这是实现GridView InsertCollapsed 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,也就是主明细表的显示,是数据库应用常见的功能,运用DataSourceGridView控件可以轻易做到这点,请建立一个网页,加入两个GridView控件,一名为GridView1,用于显示主表,二名为GridView2,用于显示明细表,接着加入两个SqlDataSource控件,一个连结至Northwind数据库的Orders数据表,另一个连结至Order Details资料表,于连结至Order Details资料表的SqlDataSource中添加WHERE条件来比对OrderID字段,值来源设成GridView1SelectedValue属性。

4-8-61

接下来请将GridView1DataSoruce设为OrdersSqlDataSourceGridView2DataSource设为Order DetailsSqlDataSource,最后将GridView1Enable Selection打勾即可完成Master-Detail的范例。

4-8-62

那这是如何办到的呢?当使用者点选GridView1上某笔数据的Select连结时,GridView1SelectedValue属性便会设成该笔数据的DataKeyName属性所指定的字段值,而连结至Order DetailsSqlDataSource又以该属性做为比对OrderID字段时的值来源,结果便成了,使用者点选了Select连结,PostBack发生,GridView2向连结至Order DetailsSqlDataSource索取资料,该SqlDataSourceGridView1.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行程序代码!!请先建立一个UserControlDetailsGrid.ascx,加入一个SqlDataSource控件连结至NorthwindOrder 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控件系结至NorthwindOrders数据表,然后加入一个GridView控件,并于其字段编辑器中加入一个TemplateField,于其内加入一个LinkButton控件,设定其属性如图4-8-66

4-8-66

然后设定LinkButtonDataBindings如图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中的LinkButtonClick事件中键入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控件的RowCreatedPageIndexChanging事件中键入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 ?相信说到这份上,读者已经知道整个程序的运行基础及概念了,剩下的细节如LoadViewStateSaveViewState皆已在前面章节提过,看懂这个范例后!你应该也想到了其它的应用了(UserControl中放DetailsViewFormViewMultiView,哈!),对于GridView!你已经毫无疑问了!

4-8-4GridView的效能

 

 OKGridView控件功能很强大,但是如果你仔细思考下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控件所需服务的不只是GridViewFormViewDetailsView都需要她,而且她们都支持分页,如果将CustomPaging直接做在这些控件上,除了控件必须有着重复的程序代码外,设计师于撰写分页程序时,也需针对不同的控件来处理,将这些移往DataSource控件后,便只会有一份程序代码。说来好听,那明摆着SqlDataSource控件就不支持分页了,那该如何解决这个问题了,答案是ObjectDataSource,这是一个支持分页的DataSource控件,只要设定几个属性及对数据提供者做适当的修改后,便可以达到手动分页的效果了。请建立一个WebiSte项目,添加一个DataSet连结到NorthwindCustomers资料表,接着新增一个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,便可以向数据库要求传回特定范围的数据。那这两个参数从何传入的呢?当GridViewObjectDataSource索取数据时,便会传入这两个参数,例如当GridViewPageSize10时,第1页时传入的startRowIndex便是10maximumRows就是10,以此类推。第二个函式是GetCount,对于GridView来说,她必须知道系结数据的总页数才能显示Pager区,而此总页数必须由资料总笔数算出,此时GridView会向PageDataSource要求资料的总笔数,而PageDataSourceDataSource控件支持分页的情况下,会要求其提供总笔数,这时此函式就会被呼叫了。大致了解这个程序后,回到设计页面,加入一个ObjectDataSource控件,于SELECT页次选取带startRowIndexmaximumRows参数的函式。

4-8-68

按下Next按纽后,精灵会要求我们设定参数值来源,请直接按下Finish来完成组态。

4-8-69

接着设定ObjectDataSourceEnablePaging属性为TrueSelectCountMethod属性为GetCount(对应了程序4-8-26中的GetCount函式),最后放入一个GridView控件,系结至此ObjectDataSource后将Enable Paging打勾,就完成了手动分页的GridView范例了。

4-8-70

在效能比上,手动分页的效能在数据量少时绝对比Cache来的慢,但数据量大时,手动分页的效能及内存耗费就一定比Cache来的好。

posted on 2007-12-28 09:45  tonyYe  阅读(1244)  评论(0编辑  收藏  举报