緣起:
3/26 那時不少的時間在弄文章標題說的事情,雖然在功能做到 70%
時才發現不需要這樣搞 (花了 7 小時),但是這個過程讓我又對 GridView
有更多的了解,所以覺得很有必要寫下來。
話說,公司那時在做稽查,我負責的專案有被抽查,還好都沒事,聽說如果被記警告的話,那些獎金好像都會沒了。
RowDataBound:
先講這個事件,之前我的這篇文章有談到,那時候的資料來源是 DataTable,然後在 RowDataBound 裡用
e.Row.Cells[index].Text 來拿值。我這次是要用 List<自訂Class>
來當資料來源,然後將資料綁到對應的控制項。
前台程式
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="GridViewTest.Test" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView runat="server" ID="myGridView" AutoGenerateColumns="false" OnRowDataBound="myGridView_RowDataBound">
<Columns>
<asp:TemplateField HeaderText="姓名">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtName"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="性別">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddlGender">
<asp:ListItem Selected="True" Value="男">男</asp:ListItem>
<asp:ListItem Value="女">女</asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="學號">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtID"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
GridView 有三個 Column,第一個跟第三個是
TextBox,第二個是一個 DropDownList,性別的選項,有男跟女。
再來是後台的程式
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace GridViewTest
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
List<MyData> listMyData = new List<MyData>();
listMyData.Add(new MyData(123, "薯泥", GenderEnum.女));
listMyData.Add(new MyData(456, "鳥旭", GenderEnum.男));
listMyData.Add(new MyData(789, "阿祈", GenderEnum.男));
listMyData.Add(new MyData(101112, "肥伶", GenderEnum.女));
myGridView.DataSource = listMyData;
myGridView.DataBind();
}
}
protected void myGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType != DataControlRowType.DataRow) return;
TextBox txtName = (TextBox)e.Row.FindControl("txtName");
TextBox txtID = (TextBox)e.Row.FindControl("txtID");
DropDownList ddlGender = (DropDownList)e.Row.FindControl("ddlGender");
txtName.Text = (string)DataBinder.Eval(e.Row.DataItem, "Name");
txtID.Text = ((int)DataBinder.Eval(e.Row.DataItem, "ID")).ToString();
ddlGender.SelectedValue = ((GenderEnum)DataBinder.Eval(e.Row.DataItem, "Gender")).ToString();
}
public class MyData
{
public int ID { get; set; }
public string Name { get; set; }
public GenderEnum Gender { get; set; }
public MyData(int iD, string name, GenderEnum gender)
{
ID = iD;
Name = name;
Gender = gender;
}
}
public enum GenderEnum
{
男,
女
}
}
}
30~51行 : 我們的資料
Class。
53~57行 :
為下拉選單設計的 Enum。
14~24行 :
模擬從資料庫撈出資料,用我們自己寫的 MyData 的 Constructor
來建立每個資料,並將它放進 List 中,最後指定 myGridView 的 DataSource,再
DataBind。
30~32行 : 在 RowDataBind
事件裡,從 .e.Row 裡用 FindControl 找出每個控制項。
34~36行 : 用 DataBinder.Eval 來取得資料,第一個傳入的項目是
e.Row.DataItem,第二個是 MyData Class
的欄位名稱,要注意資料型態的轉換。
最後呈現的結果會是這樣
加入新一列:
我在前台加入一個按鈕,使用者點擊後,GridView
會在最下方加入新的一列。為了在 PostBack 後還保留每列的使用者輸入,所以需要把
GridView 每個 Row 的控制項的值給抓出來
前台
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="GridViewTest.Test" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button runat="server" ID="btnAddRow" Text="新增資料" OnClick="btnAddRow_Click"/><br />
<br />
<asp:GridView runat="server" ID="myGridView" AutoGenerateColumns="false" OnRowDataBound="myGridView_RowDataBound">
<Columns>
<asp:TemplateField HeaderText="姓名">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtName"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="性別">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddlGender">
<asp:ListItem Selected="True" Value="男">男</asp:ListItem>
<asp:ListItem Value="女">女</asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="學號">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtID"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
13行 : 加入一個 Button
再來是後台程式碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace GridViewTest
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
List<MyData> listMyData = new List<MyData>();
listMyData.Add(new MyData(123, "薯泥", GenderEnum.女));
listMyData.Add(new MyData(456, "鳥旭", GenderEnum.男));
listMyData.Add(new MyData(789, "阿祈", GenderEnum.男));
listMyData.Add(new MyData(101112, "肥伶", GenderEnum.女));
myGridView.DataSource = listMyData;
myGridView.DataBind();
}
}
protected void myGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType != DataControlRowType.DataRow) return;
TextBox txtName = (TextBox)e.Row.FindControl("txtName");
TextBox txtID = (TextBox)e.Row.FindControl("txtID");
DropDownList ddlGender = (DropDownList)e.Row.FindControl("ddlGender");
txtName.Text = (string)DataBinder.Eval(e.Row.DataItem, "Name");
txtID.Text = ((int)DataBinder.Eval(e.Row.DataItem, "ID")).ToString();
ddlGender.SelectedValue = ((GenderEnum)DataBinder.Eval(e.Row.DataItem, "Gender")).ToString();
}
protected void btnAddRow_Click(object sender, EventArgs e)
{
List<MyData> listMyData = _RetrieveData();
listMyData.Add(new MyData(1, "", GenderEnum.男));
myGridView.DataSource = listMyData;
myGridView.DataBind();
}
private List<MyData> _RetrieveData()
{
List<MyData> listReturn = new List<MyData>();
foreach (GridViewRow gridViewRow in myGridView.Rows)
{
TextBox txtName = (TextBox)gridViewRow.FindControl("txtName");
TextBox txtID = (TextBox)gridViewRow.FindControl("txtID");
DropDownList ddlGender = (DropDownList)gridViewRow.FindControl("ddlGender");
string sName = txtName.Text;
int iID = 0;
int.TryParse(txtID.Text, out iID);
GenderEnum gender;
Enum.TryParse(ddlGender.SelectedValue, out gender);
listReturn.Add(new MyData(iID, sName, gender));
}
return listReturn;
}
public class MyData
{
public int ID { get; set; }
public string Name { get; set; }
public GenderEnum Gender { get; set; }
public MyData(int iD, string name, GenderEnum gender)
{
ID = iD;
Name = name;
Gender = gender;
}
}
public enum GenderEnum
{
男,
女
}
}
}
47~66行 : 將 myGridView
的每列控制項所存的值取出,存回 MyData class,最後回傳
List<MyData>。
50行 : myGridView.Rows
取得所有的 Row,用 GridViewRow 來遊歷每一列。
52~54行 : 呼叫 GridViewRow 的
findControl 來找到每個控制項。
56~60行 :
將每個控制項的值取出,存到變數中。Enum 可以用 Enum.TryParse 來解析 String。
62、65行 : 用記錄的變數來 new
一個 MyData,存到 List 中,最後回傳 List。
39~45行 : 新增列按鈕的 click
事件。
41行 : 用我們寫的 _RetrieveData
先把 myGridView 的資料取出。
42行 : 在 List
最後面加入一個 MyData
43、44行 : 重新指定
myGridView 的 DataSource
完成後,開啟我們的網頁,點擊按鈕後,它會新增一列出來
刪除指定列:
現在,我要在 myGridView 加入一個新的 column,裡面放的是一個
"刪除"
的按鈕。在實做刪除資料前,我要先能夠得知是哪一列的刪除按鈕被點擊,所以開始寫測試的程式碼
前台程式碼
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="GridViewTest.Test" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button runat="server" ID="btnAddRow" Text="新增資料" OnClick="btnAddRow_Click"/><br />
<br />
<asp:Literal runat="server" ID="litTest"></asp:Literal>
<br />
<asp:GridView runat="server" ID="myGridView" AutoGenerateColumns="false" OnRowDataBound="myGridView_RowDataBound" OnRowCommand="myGridView_RowCommand">
<Columns>
<asp:TemplateField HeaderText="姓名">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtName"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="性別">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddlGender">
<asp:ListItem Selected="True" Value="男">男</asp:ListItem>
<asp:ListItem Value="女">女</asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="學號">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtID"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="功能">
<ItemTemplate>
<asp:Button runat="server" ID="btnDelete" Text="刪除" CommandName="deleteRow"/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
15行 : 加入 "litTest" 的
Literal,用來顯示是哪一列的刪除鈕被點擊。
17行 : GridView 加入
OnRowCommand 事件。
37~41行 : 放刪除按鈕的
Column。我注意到,如果 CommandName 叫 "delete" 的話,ASP 會直接幫我們觸發
Delete 的事件,所以我這邊才叫它 "deleteRow"。
後台程式碼,由於只比上次多了 myGridView_RowCommand
這個方法,所以就單獨截它出來講
protected void myGridView_RowCommand(object sender, GridViewCommandEventArgs e)
{
if(e.CommandName == "deleteRow")
{
Button button = (Button)e.CommandSource;
GridViewRow gridViewRow = (GridViewRow)button.NamingContainer;
litTest.Text = gridViewRow.RowIndex.ToString();
}
}
3行 : RowCommand
傳進來的是 GridViewCommandEventArgs 型態的參數,比對 CommandName
來選擇要執行的動作。
5行 : 可以用 CommandSource
來取得事件的觸發者,這裡是 Button。
6行 : 從這裡學到的,可以用 NamingContainer 來取得它所屬的 GridViewRow。
8行 : 從 GridViewRow 的
RowIndex 可以得知它是第幾列 (從0開始算)。
執行程式,點擊第一列的刪除按鈕,可以看到 Literal 顯示
0
既然能成功取得要刪除的列的 index,那刪除也就簡單了,修改我們的 myGridView_RowCommand 事件,呼叫 myGridView 的 DeleteRow,傳入 gridViewRow.Index
protected void myGridView_RowCommand(object sender, GridViewCommandEventArgs e)
{
if(e.CommandName == "deleteRow")
{
Button button = (Button)e.CommandSource;
GridViewRow gridViewRow = (GridViewRow)button.NamingContainer;
myGridView.DeleteRow(gridViewRow.RowIndex);
}
}
但是呢,事情好像沒有我們想像的簡單,在我們開啟網頁,點擊按鈕後,它跳出錯誤。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="GridViewTest.Test" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button runat="server" ID="btnAddRow" Text="新增資料" OnClick="btnAddRow_Click"/><br />
<br />
<asp:Literal runat="server" ID="litTest"></asp:Literal>
<br />
<asp:GridView runat="server" ID="myGridView" AutoGenerateColumns="false" OnRowDataBound="myGridView_RowDataBound" OnRowCommand="myGridView_RowCommand" OnRowDeleting="myGridView_RowDeleting">
<Columns>
<asp:TemplateField HeaderText="姓名">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtName"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="性別">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddlGender">
<asp:ListItem Selected="True" Value="男">男</asp:ListItem>
<asp:ListItem Value="女">女</asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="學號">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtID"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="功能">
<ItemTemplate>
<asp:Button runat="server" ID="btnDelete" Text="刪除" CommandName="deleteRow"/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
17行 : myGridView 加入
OnRowDeleting 事件。跟前次的主要差別是多了 myGridView_RowDeleting 事件
後台的完整程式碼會是這樣
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace GridViewTest
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
List<MyData> listMyData = new List<MyData>();
listMyData.Add(new MyData(123, "薯泥", GenderEnum.女));
listMyData.Add(new MyData(456, "鳥旭", GenderEnum.男));
listMyData.Add(new MyData(789, "阿祈", GenderEnum.男));
listMyData.Add(new MyData(101112, "肥伶", GenderEnum.女));
myGridView.DataSource = listMyData;
myGridView.DataBind();
}
}
protected void myGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType != DataControlRowType.DataRow) return;
TextBox txtName = (TextBox)e.Row.FindControl("txtName");
TextBox txtID = (TextBox)e.Row.FindControl("txtID");
DropDownList ddlGender = (DropDownList)e.Row.FindControl("ddlGender");
txtName.Text = (string)DataBinder.Eval(e.Row.DataItem, "Name");
txtID.Text = ((int)DataBinder.Eval(e.Row.DataItem, "ID")).ToString();
ddlGender.SelectedValue = ((GenderEnum)DataBinder.Eval(e.Row.DataItem, "Gender")).ToString();
}
protected void btnAddRow_Click(object sender, EventArgs e)
{
List<MyData> listMyData = _RetrieveData();
listMyData.Add(new MyData(1, "", GenderEnum.男));
myGridView.DataSource = listMyData;
myGridView.DataBind();
}
private List<MyData> _RetrieveData()
{
List<MyData> listReturn = new List<MyData>();
foreach (GridViewRow gridViewRow in myGridView.Rows)
{
TextBox txtName = (TextBox)gridViewRow.FindControl("txtName");
TextBox txtID = (TextBox)gridViewRow.FindControl("txtID");
DropDownList ddlGender = (DropDownList)gridViewRow.FindControl("ddlGender");
string sName = txtName.Text;
int iID = 0;
int.TryParse(txtID.Text, out iID);
GenderEnum gender;
Enum.TryParse(ddlGender.SelectedValue, out gender);
listReturn.Add(new MyData(iID, sName, gender));
}
return listReturn;
}
protected void myGridView_RowCommand(object sender, GridViewCommandEventArgs e)
{
if(e.CommandName == "deleteRow")
{
Button button = (Button)e.CommandSource;
GridViewRow gridViewRow = (GridViewRow)button.NamingContainer;
myGridView.DeleteRow(gridViewRow.RowIndex);
}
}
protected void myGridView_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
List<MyData> listMyData = _RetrieveData();
listMyData.RemoveAt(e.RowIndex);
myGridView.DataSource = listMyData;
myGridView.DataBind();
}
public class MyData
{
public int ID { get; set; }
public string Name { get; set; }
public GenderEnum Gender { get; set; }
public MyData(int iD, string name, GenderEnum gender)
{
ID = iD;
Name = name;
Gender = gender;
}
}
public enum GenderEnum
{
男,
女
}
}
}
78~84行 : myGridView_RowDeleting 事件。
80行 : 把 myGridView 現存的資料取出。
81行 : RowDeleting 會傳入 GridViewDeleteEventArgs 參數,可以從 RowIndex 得知是哪列被刪,搭配 List 的 RemoveAt 方法來移除資料。
82、83行 : 重新指定 DataSource 並 Bind。
這樣,之後不管是在新增或刪除列,其它列的資料就不會因為 PostBack 而消失不見。
沒有留言:
張貼留言