简介
DataGrid服务器控制是从数据源显示信息的强有力的工具,使用简单。你可以只通过设定一些属性就可以显示可编辑的数据,并且使其具有专业的外观,同时,它还具有非常复杂的对象模型,为你展示数据提供了很大的灵活性。
这篇文章重点讲解了在NewsGroup网络和其它的开发人员的论坛里经常问到的关于制定DataGrid的显示的问题。这里所讨论的有些问题是很简单的,但有时也有些令人迷惑,在这种情况下,他们提出了这一问题——如何掌握DataGrid控件的基本功能。
序言:Windows窗体&Web窗体中的DataGrid控件。
Web窗体中的DataGrid控件并不是Windows窗体中该控件的等价物,通常认为它们是同一种控件,至少具有相同的功能。但是,Web窗体的整个编程Paradigm和Windows窗体有着很大的不同,举个例子来说,Web窗体页面的每一次处理过程都要回传到服务器,它们必须维护状态,而且具有非常不同的数据绑定模型,如此等等。
②.Web窗体DataGrid控件支持分页。
③.与Windows窗体DataGrid相比,我们更容易规范Web窗体DataGrid的外观和数据的输出。
Header Text=”Title”>
<ItemStyle Width=”100px”></ItemStyle>
</asp:BoundColumn>
注:可以将ItemStyle-Width=”100px”写入Boundolumn中。
你可以使用样式元素设置对齐方式,将其设为“左”、“右”或者其它在“水平对齐”集合中定义的值(在VS中,每个单独的列对齐方式是可见的)
下面是一个例子:
<asp: BoundColumn DataField=”title” SortExpression=”title”
HeaderText=”Title”>
<ItemStyle Width=”100px”HorizontalAlign=”Right”></ItemStyle>
</asp:BoundColumn>
你还可以使用样式元素设定列的高度,你很可能会觉得这没有设定宽度灵活,因为设定某一列的高度也就设定了全部列的高度。
你也可以在运行时通过代码设定宽度,采取这种方式的一个例子就是ItemGreated事件句柄,下面的例子将前两列分别设定为100和50像素。
//C#
Private void DataGrid-ItemCreated(object sender,
System.Web.UI.WebControls.DataGridItemEventArgs e)
{
e.Item.Cells[0]Width=new Unit(100);
e.Item.Cells[1].Width=new Unit(50);
}
当然,将这些可以在设计时就设置好的宽度值通过代码来设置几乎没有任何意义。一般情况下,你可能只是想要基于运行时的值来设定宽度而采取这种方法,你可以将单元格控件的宽度设为标准的单位(往往是像素)。但是,它并不直接将这些长度数据转化为像素——这些仅仅是字符数。然而,当你创建一个项的时候,可以使用这些数据来检查。
在显示和编辑模式下自定义列的外观
默认情况下,DataGrid用预设大小的列显示数据。当你将行设置为编辑模式时,该控件将所有可编辑数据显示在TextBox中,不管是什么类型的数据。
如果你想自定义列的内容,则将列设定为模板列,模板列的工作方式与Dataist或者Repeater控件中的项模板类似。差别之处仅在于你定义的是列的外观不是行的外观。
定义模板列的时候,可以具体设定下面这些模板类型:
① 使用项模板来自定义数据的一般外观。
② 将行设为编辑模式时使用编辑项模板来具体设定列的显示。它允许你具体设置一个控件而不是编辑模式下默认的TextBox.
③ Header模板和Footer模板允许你分别自定义控件的Header和Footer.(只有将DataGrid的ShowFoot属性设为True,才会显示表的尾部。)
下面的例子是一个显示布尔数据的模板列的HTML语法。项模板和编辑模板都使用一个CheckBox来显示值。项模板中的CheckBox是不可用的,使用者就不能勾选它,而在编辑项模板中CheckBox好似允许用户选择的。
<columns>
<asp:TemplateColumn HeaderText=”Discontinued”>
<ItemTemplate>
<asp:Checkbox runat =”server”enabled=false name=”Checkbox 2”
ID=”checkbox 2”
Checked=’<%#DataBinder.Eval(Container,”Dataltem.Discontinued”)%>’>
</asp:Checkbox>
</ItemTemplate>
<EditltemTemplate>
<asp:Checkbox
runat=”server”name=”Checkbox 2”ID=”Checkbox 2”
Checked=’<%#DataBinder.Eval(Container,”DataItem.Discontinued”)%>’>
</asp:Checkbox>
</EditltemTemplate>
</asp:TEmplateColumn>
</Columns>
注意:如果在编辑模板中使用一个CheckBox,要意识到在运行时,表项单元格实际上还包含了除了CheckBox自身以外的几个LiteralControl控件,任何时候你知道了你想要的值的控件的ID,使用FindControl方法来创建对它的引用,而不是使用它在单元格集合或者控件集合的特定的索引值。
CheckBox cb;
cb=(CheckBox)e.Item.FindControl(“CheckBox 2”);
在VS中。可以使用DataGrid的属性生成器来创建模板列,并使用模板编辑器来具体设定它的外观,在属性窗口页的列选择中,选中该列并单击底部的“将该列转化为模板列”,关闭属性窗口,右击该DataGrid并选择“编辑模板”,然后,你就可以从工具箱拖拽控件到模板中,也可以增加静态文本。
格式化日期、货币和其它数据
DataGrid控件中的信息最终是显示在Web窗体页中的一个HTML表格中,因此,为了控制数据如何显示,你可以为每列的值来具体设置.NET字符串的格式,当DataGrid的AutoGenerateColumns属性设为TRUE时,不允许具体设定生成列的格式,只能对绑定列或者模板列如此设定。
为格式化数据,需要将列的“数据格式字符串”属性设置为适应数据的数据类型的一个字符串格式表达式,格式化字符串的稍复杂之处在于:同样的指定字符,比如D,可以根据不同的结果应用于不同的数据类型(整型、日期)
注意:在VS中,你可以在属性生成器的列标签中指定格式化表达式。
下表列出了格式化字符串的一些例子,可以通过查看VS文挡中的“格式化类型”和“BoundColumn.DataFormatting属性”主题得到更多信息。
应用于某一数据类型描述的格式化表达式:
Price:{0:c}
注意:{0}是一个0,不是字母O,数值或十进制数以货币格式显示在字符“Price:”后,通过直接设定Page或Web.config文件的Culture属性可以指定不同的货币格式。
{0:D4} 适用于整型(不能用于十进制数据)。四位一组,空位补零。
{0:N2}% 适用于十进制数。在字符%前显示数字,精确到小数点后两位。
{0:000.0} 适用于数字/十进制数。小数点后保留一位,小数点前不足三位的补零。
{0:D} 适用于日期/日期时间。采用长时间格式如: ("Thursday, August 06, 1996").
日期格式取决于page页或者Web.config 文件的culture设置.
{0:d} 适用于日期/日期时间。采用短日期格式,如 (" 12/31/99").
{0:yy-MM-dd}适用于日期/时间,以年-月-日格式显示,如: ( 96-08-06).
动态显示和隐藏列&动态增加列
——动态显示隐藏列
动态显示列的一种方式就是在设计时创建它们,然后在需要时隐藏或显示它们,可以通过设定列的Visible属性来实现。下面的例子说明了如何控制DataGrid的第二列(索引为一的列)的可见性。
DataGrid1.Column[1].visible=!(DataGrid1.Column[1].visible);
——动态增加列(这里列都是由数据库读出的列)
如果你事先知道需要哪些列,你可以隐藏或显示它们,但是,有时你不到运行时是不知道需要哪些列的,在这种情况下,你可以动态创建列,并将其加入DataGrid中。
实现的方法是创建一个DataGrid支持的列类型的一个实例——绑定列、按钮列、或者超链接列(还可以添加模板列,但是稍微复杂些)。
下例说明如何绑定两个列到 DataGrid中:
private void Button1_Click(object sender, System.EventArgs e)
{
DataGrid1.AutoGenerateColumns = false;
DataGrid1.DataSource = this.dsBooks1;
DataGrid1.DataMember = "Books";
DataGrid1.DataKeyField = "bookid";
// Add two columns
BoundColumn dgc_id = new BoundColumn();
dgc_id.DataField = "bookid";
dgc_id.HeaderText = "ID";
dgc_id.ItemStyle.Width = new Unit(80);
DataGrid1.Columns.Add(dgc_id);
BoundColumn dgc_title= new BoundColumn();
dgc_title.DataField = "title";
dgc_title.HeaderText = "Title";
DataGrid1.Columns.Add(dgc_title);
this.sqlDataAdapter1.Fill(this.dsBooks1);
DataGrid1.DataBind();
}
任何时候向列中动态增加控件,都会遇到如何保持的问题。动态增加的列并非自动地添加到page的视图态,因此,你需要添加page的逻辑,使得在每次提交—回传的过程中,列是可见的。
实现上述问题的一个优秀的方法就是重载page的LoadViewState方法,它为我们提供了重新确定DataGrid控件中列的较早机会,因为该方法的调用是在Page_Load事件触发之前。在LoadViewState中重新添加列保证了它们在任何事件代码执行前,对正常的操作是可用的。
下例展示了如何扩展上例使得每次page执行时都将列重新保存。和上例一样,Button1_Click事件向DataGrid中添加了两列(该例中,事件句柄调用了一个独立的方法——AddColumns()来实现),而且,page还包含了一个简单的布尔属性,称为DynamicColumnsAdded来指示DataGrid中是否有列动态增加。该属性在视图态下保存它的值。LoadViewState方法首先调用它的基类的LoadViewState方法,它摘录视图态信息并配置控件。如果列事先被添加到DataGrid中,该方法就再次添加它们。
private bool DynamicColumnAdded{
get
{
object b = ViewState["DynamicColumnAdded"];
return (b == null) ? false : true;
}
set
{
ViewState["DynamicColumnAdded"] = value;
}
}
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
if (DynamicColumnAdded)
{
this.AddColumns();
}
}
private void Button1_Click(object sender, System.EventArgs e)
{
if(this.DynamicColumnAdded != true)
{
this.AddColumns();
}
}
private void AddColumns()
{
BoundColumn dgc_id = new BoundColumn();
dgc_id.DataField = "bookid";
dgc_id.HeaderText = "ID";
dgc_id.ItemStyle.Width = new Unit(80);
DataGrid1.Columns.Add(dgc_id);
BoundColumn dgc_title= new BoundColumn();
dgc_title.DataField = "title";
dgc_title.HeaderText = "Title";
DataGrid1.Columns.Add(dgc_title);
this.sqlDataAdapter1.Fill(this.dsBooks1);
DataGrid1.DataBind();
this.DynamicColumnAdded = true;
}
使用DataGrid控件向数据源增加一条记录
DataGrid控件允许用户查看和编辑记录,但内部不包含添加新记录的便利,但是,可以采取不同的方法来添加这个功能。下面的方法包含了如下步骤:
l 向DataGrid的数据源(DataSet or DataBase)增加一个新的、空的记录,如果需要的话,还要为记录指定一个ID并为每个不允许为空的列存入一个占位的值。
l 重绑定DataGrid到数据源。
l 将DataGrid转变为编辑模式,你需要能够知道新记录在DataGrid中显示的位置。
l 使用普通的方式更新记录——用户单击“Update”,然后将用户提供的值作为新的记录的值写回到数据源。
下面是添加新记录、绑定DataGrid,以及将其转化为编辑模式的例子。该例中的数据
源是一个DataSet(dsBook1),它包含了一个Books表。
private void btnAddRow_Click(object sender, System.EventArgs e)
{
DataRow dr = this.dsBooks1.Books.NewRow();
dr["title"] = "(New)";
dr["instock"] = true;
this.dsBooks1.Books.Rows.InsertAt(dr, 0);
Session["DsBooks"] = dsBooks1;
DataGrid1.EditItemIndex = 0;
DataGrid1.DataBind();
}
需要注意的一些问题:
l 当用户点击也面中的某个“Add”按钮时,这些代码就被执行。
l 新行通过NewRow()方法生成,然后通过InsertAt()方法插入到DataSet中的表,该方法允许将该行设定到一个具体的预先确定的位置——在这个例子中,它是表中的第一条记录(也就是Rows集合中的第一行)。换种方式,你也可以通过设置行的数目值将其插入表尾。重要的是,要确切知道该行在表中的位置。
l 由于现在你已经知道该行在表中的第一个位置上,你可以通过将DataGrid的编辑项索引设为0,从而将新行设置为编辑模式(如果你在表中的其他位置创建了新行,你可以将编辑项索引设为那个位置的值)。
l 现在在数据集中已经有了一条新的记录(但它还不是在数据库中),如果你不想从数据库重新填充DataGrid从而丢失新的记录,就需要在提交—回传的过程中保留一个该数据集的副本。例子中通过代码将其保存在Session中,当页加载时,需要重新从Session中加载数据集。下例是Page_Load()事件句柄的一种可能形式:
private void Page_Load(object sender, System.EventArgs e)
{
if(this.IsPostBack)
{
dsBooks1 = (dsBooks) Session["DsBooks"];
}
else
{
this.sqlDataAdapter1.Fill(this.dsBooks1);
Session["DsBooks"] = dsBooks1;
this.DataGrid1.DataBind();
}
}
你可以通过查看VS文档中的《WEB窗体状态管理》来获得维护状态的信息。
你可以正常更新记录。比如查看VS文档中的概览——使用DataGrid控件读写数据。更
新DataSet后,更新数据库,然后刷新DataSet。要保证再次将刷新后的DataSet保存到Session态。下面是一个更新事件句柄的例子。
private void DataGrid1_UpdateCommand(object source,
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
dsBooks.BooksRow dr;
//Get a reference to row zero (where the row was inserted)
dr = this.dsBooks1.Books[0];
TextBox tb1 = (TextBox) e.Item.Cells[2].Controls[0];
dr.title = tb1.Text;
CheckBox cb = (CheckBox) e.Item.Cells[3].Controls[1];
dr.instock = cb.Checked;
this.sqlDataAdapter1.Update(this.dsBooks1);
DataGrid1.EditItemIndex = -1;
//Refresh the dataset from the database
dsBooks1.Clear();
this.sqlDataAdapter1.Fill(this.dsBooks1);
//Save the refreshed dataset in Session state agin
Session["DsBooks"] = dsBooks1;
DataGrid1.DataBind();
}
编辑模式下显示一个Drop-Down List
这里将提供给用户的一个常用需求——一个某行处于编辑状态下的Drop-Down List。举例来说,一个DataGrid可能显示一个书籍清单,其中包括每本书的类别,当用户编辑一条书籍记录时,可能要为该书指定一个不同的类别,理想情况下,他们可以从Drop-Down List中选择可能的类别值,比如“小说”、“生物”或者“参考书目”。
显示一个Drop-Down List需要在DataGrid中设定一个模板列。典型的情况是:项模板包含了一个控件,比如一个数据绑定的Label控件来显示记录中某个字段的当前值。然后,向编辑项模板中添加一个Drop-Down List,在VS中,你可以通过DataGrid的属性生成器添加一个模板列,然后通过“编辑模板”移除编辑项模板中默认的TextBox控件,并拖入一个Drop-Down List控件,你还可以在HTML视图下添加模板列。
在创建了含有Drop-Down List的模板列后还有两个任务:首先,要产生一个列表,其次要设定列表中的预选项。举例来说,如果本书的类别设为“小说”,当Drop-Down List显示时,往往需要“小说”是预选的(预选一个项并不是在所有情况下都要讨论的问题)。
可以有多种生成Drop-Down List的方法。下面的几个例子说明了三种方式:使用静态项;使用DataSet中的记录集合;使用DataReader直接从数据库中读取信息。
l 静态项:在Drop-Down List中显示静态项不需要绑定数据到控件。你可以很简单地在控件的项集合中定义项。在VS中,可以通过属性窗口中的Items属性触发项集合编辑器,也可以在HTML视图中编辑项。
下面是一个显示模式下显示类别的模板列和编辑模式下类别的静态列表的完整定义。项模板包含了一个Label控件,其Text属性被设置为当前记录的“genre”(类别)字段,编辑项模板中的静态项定义高亮显示。
<asp:TemplateColumn HeaderText="genre">
<ItemTemplate>
<asp:Label id=Label4 runat="server"
Text='<%# DataBinder.Eval(Container, "DataItem.genre") %>'>
</asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList id="DropDownList2" runat="server" Width="172px">
<asp:ListItem Value="fiction">fiction</asp:ListItem>
<asp:ListItem Value="biography">biography</asp:ListItem>
<asp:ListItem Value="reference">reference</asp:ListItem>
</asp:DropDownList>
</EditItemTemplate>
</asp:TemplateColumn>
l DataSet
如果你想要在Drop-Down List中显示的数据处于一个DataSet里,那么你可以采用通用的数据绑定方式。下面是它的声明语法。Drop-Down List绑定了数据集DsBooks1中的类别表。数据绑定的设置高亮显示。
<asp:TemplateColumn HeaderText="genre (dataset)">
<ItemTemplate>
<asp:Label id=Label3 runat="server"
Text='<%# DataBinder.Eval(Container, "DataItem.genre") %>'>
</asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList id=DropDownList4 runat="server"
DataSource="<%# DsBooks1 %>" DataMember="Genre"
DataTextField="genre" DataValueField="genre" Width="160px">
</asp:DropDownList>
</EditItemTemplate>
</asp:TemplateColumn>
l DataReader
你也可以直接从数据库生成Drop-Down List。这种方式更为复杂但效率也更高。原因在于只有在需要的时候才去读数据库。
实现的一个相当简便的方法是利用WEB窗体数据绑定表达式。尽管最常用的是在数据绑定表达式中调用DataBinder.Eval 方法,但事实上你可以使用该页任何可用的public成员。下面的例子告诉你如何创建一个函数来创建、填充以及返回一个DataTable对象,Drop-Down List可以对它进行绑定。
这种情况下,你需要能够执行一个能得到你需要的记录集的数据命令。比如说,你可能需要定义一个Command,并把它的CommandText属性设为“SELECT * FROM Genres”。为了简化例子,假设在页面中已经有了一个Connection对象和一个Command对象。
由创建页面中的一个共有函数开始。这个函数创建了一个DataTable对象并定义了你需要的列集合。然后打开连接,执行Command命令来返回一个DataReader,并且遍历DataReader,复制数据到DataTable,最后,将DataTable作为该函数的返回值返回。
下面说明了如何实现。该例中返回的DataTable中只有一列(“genre”)。通常只需要一个列来生成Drop-Down List。如果需要将其Text和Value设成不同的值,就需要两个列。
public DataTable GetGenreTable()
{
DataTable dtGenre = new DataTable();
if(Application["GenreTable"] == null)
{
DataRow dr;
DataColumn dc = new DataColumn("genre");
dtGenre.Columns.Add(dc);
this.sqlConnection1.Open();
System.Data.SqlClient.SqlDataReader dreader =
this.sqlCommand1.ExecuteReader();
while(dreader.Read())
{
dr = dtGenre.NewRow();
dr[0] = dreader[0];
dtGenre.Rows.Add(dr);
}
this.sqlConnection1.Close();
}
else
{
dtGenre = (DataTable) Application["GenreTable"];
}
return dtGenre;
}
注意:该函数将它创建的DataTable保存于Application态,由于这里的DataTable就象一个静态的查询表,所以并不需要在每次一个不同行转为编辑模式时都重新读取它,而且,由于同一个DataTable可以被多用户使用,可以将其保存在全局的Application态而不是保存在根据用户不同而不同的Session态。
下面是模板列的声明,你会发现这与绑定DataSet中表的语法非常相似,唯一的真正区别就是数据源的绑定调用的是你自己的函数。这种技术的稍有不利之处在于,在VS中,得不到太多的设计类型服务。由于是通过代码定义DataTable来绑定,VS不会提供任何方式来对DataMember、DataTextField和DataValueField属性进行设置,需要你自己来确定这些属性,使它们与你在代码中创建的成员名字相匹配。
<asp:TemplateColumn HeaderText="genre (database)">
<ItemTemplate>
<asp:Label id=Label1 runat="server"
Text='<%# DataBinder.Eval(Container, "DataItem.genre") %>'>
</asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList id=DropDownList1 runat="server"
DataSource="<%# GetGenreTable() %>"
DataMember="Genre"
DataTextField="genre"
DataValueField="genre"
Width="120px">
</asp:DropDownList>
</EditItemTemplate>
</asp:TemplateColumn>
③.与Windows窗体DataGrid相比,我们更容易规范Web窗体DataGrid的外观和数据的输出。
Header Text=”Title”>
<ItemStyle Width=”100px”></ItemStyle>
</asp:BoundColumn>
注:可以将ItemStyle-Width=”100px”写入Boundolumn中。
你可以使用样式元素设置对齐方式,将其设为“左”、“右”或者其它在“水平对齐”集合中定义的值(在VS中,每个单独的列对齐方式是可见的)
下面是一个例子:
<asp: BoundColumn DataField=”title” SortExpression=”title”
HeaderText=”Title”>
<ItemStyle Width=”100px”HorizontalAlign=”Right”></ItemStyle>
</asp:BoundColumn>
你还可以使用样式元素设定列的高度,你很可能会觉得这没有设定宽度灵活,因为设定某一列的高度也就设定了全部列的高度。
你也可以在运行时通过代码设定宽度,采取这种方式的一个例子就是ItemGreated事件句柄,下面的例子将前两列分别设定为100和50像素。
//C#
Private void DataGrid-ItemCreated(object sender,
System.Web.UI.WebControls.DataGridItemEventArgs e)
{
e.Item.Cells[0]Width=new Unit(100);
e.Item.Cells[1].Width=new Unit(50);
}
当然,将这些可以在设计时就设置好的宽度值通过代码来设置几乎没有任何意义。一般情况下,你可能只是想要基于运行时的值来设定宽度而采取这种方法,你可以将单元格控件的宽度设为标准的单位(往往是像素)。但是,它并不直接将这些长度数据转化为像素——这些仅仅是字符数。然而,当你创建一个项的时候,可以使用这些数据来检查。