[ASP.net] 巢狀GridView (塞資料行、塞資料列兩種寫法)
GridView包GridView、GridView塞GridView,在網路上找到有兩種
1. 子GridView塞主GridView目前的資料行
2. 子GridView塞主GridView的下一資料列
第一種寫法:
這邊我就偷懶一下,拿MSDN論壇裡,TerryChuang大大的寫法來改:GridView裡的每一列,再塞入另一個GridView
原本按Close時,子GridView所在的資料行會一律隱藏,稍做改寫 判斷當都沒有子GridView顯示時,才把子GridView所在的資料行隱藏
.aspx
<%@ Page Language="C#" Debug="true" AutoEventWireup="true" CodeFile="subGridViewAddInColumn.aspx.cs" Inherits="subGridViewAddInColumn" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:GridView ID="GridView1" AutoGenerateColumns="false" runat="server" DataKeyNames="OrderID"
onrowcommand="GridView1_RowCommand" onrowcreated="GridView1_RowCreated">
<Columns>
<asp:BoundField HeaderText="OrderID" DataField="OrderID" />
<asp:BoundField HeaderText="CustomerID" DataField="CustomerID" />
<asp:TemplateField Visible="false">
<ItemTemplate>
<asp:GridView ID="GridView2" runat="server" Visible="false" >
</asp:GridView>
<!--預設值Visible="false"是為了收合時判斷要不要把此資料行隱藏-->
</ItemTemplate>
</asp:TemplateField>
<asp:ButtonField CommandName="Expand" HeaderText="異動" Text="Expand" ButtonType="Button" />
</Columns>
</asp:GridView>
</form>
</body>
</html>
.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
public partial class subGridViewAddInColumn : System.Web.UI.Page
{
string Conn_E = WebConfigurationManager.ConnectionStrings["NorthwindChineseConnectionString"].ConnectionString;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GV1Bind();
}
}
private void GV1Bind()
{
GridView1.DataSource = this.queryDataTable("Select * from Orders Order by OrderID ASC");
GridView1.DataBind();
}
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
//Command按鈕的Argument給RowIndex,至於資料表的PK值,可以再利用GridView1.DataKeys[rowIndex].Value來取得
(e.Row.Cells[3].Controls[0] as Button).CommandArgument = e.Row.RowIndex.ToString();
}
}
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
int rowIndex = int.Parse(e.CommandArgument.ToString());//RowIndex
GridView gv2 = GridView1.Rows[rowIndex].FindControl("GridView2") as GridView;
int orderID = int.Parse(GridView1.DataKeys[rowIndex].Value.ToString());
Button btn = GridView1.Rows[rowIndex].Cells[3].Controls[0] as Button;
//展開
if (e.CommandName == "Expand")
{
gv2.DataSource = this.queryDataTable("Select * from OrderDetails Where OrderID = '"+orderID+"'");
gv2.DataBind();
gv2.Visible = true;
GridView1.Columns[2].Visible = true;
btn.Text = "Close";
btn.CommandName = "Colse";
}
//收合
else
{
int count = 0;
foreach (GridViewRow gvr in GridView1.Rows)//走訪目前GridView1呈現的DataRow集合
{
GridView gv_2 = (GridView)gvr.FindControl("GridView2");
if (gv_2.Visible==true)//至少有一個gv2有顯示,不知道為啥用DataSource當旗標來判斷會不準
{
count++;
}
}
//Debug用
//Response.Write(count+"<br/>");
if (count >= 2)
{
GridView1.Columns[2].Visible = true;
}
else
{
GridView1.Columns[2].Visible = false;
}
gv2.DataSource = null;
gv2.DataBind();
gv2.Visible = false;
btn.Text = "Expand";
btn.CommandName = "Expand";
}
}
//撈出資料集
public DataTable queryDataTable(string sql)
{
using (SqlConnection conn = new SqlConnection(this.Conn_E))
{
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
DataSet ds = new DataSet();
da.Fill(ds);
return (ds.Tables.Count > 0) ? ds.Tables[0] : new DataTable();
}
}
}
執行效果:
展開前兩列
收合第二列
再收合第一列(第3個資料行才隱藏)
第二種寫法(把子GridView塞在每一筆資料列的下一個資料列):
這邊本來使用[ASP.net] GridView新增、移除列來改寫,但發現實現結果,從上往下點展開要點兩次才展得開,由下往上點只要點一次就行(?)的奇怪現象
所以想來想去,還是在GridView1_RowDataBound事件,先把子GridView塞在每筆資料列的下一列,然後該列隱藏
讓按鈕只是個開關,用jQuery控制顯示和隱藏,這樣做每次展開、收合時不用跟Server端溝通,速度還比較快
.aspx
<%@ Page Language="C#" Debug="true" AutoEventWireup="true" CodeFile="GridViewRowAddByRow.aspx.cs" Inherits="GridViewRowAddByRow" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
<script type="text/javascript" src="js/jquery-1.4.1.min.js"></script>
<script type="text/javascript">
function showSubTable(index) {
var targetTrClass = "master" + index + "_" + index;
$("tr." + targetTrClass).toggle();
/*改變input的文字*/
var inputValue = $("input." + "master" + index).val();
if (inputValue=="Open") {
$("input." + "master" + index).val("Close");
} else {
$("input." + "master" + index).val("Open");
}
}
</script>
</head>
<body>
<form id="form1" name="form1" runat="server">
<asp:GridView ID="GridView1" AutoGenerateColumns="False" runat="server" onrowdatabound="GridView1_RowDataBound" >
<Columns>
<asp:BoundField HeaderText="OrderID" DataField="OrderID" />
<asp:BoundField HeaderText="CustomerID" DataField="CustomerID" />
<asp:TemplateField HeaderText="異動" ShowHeader="False">
<ItemTemplate>
<input type="button" onclick='showSubTable(<%# Container.DataItemIndex+1%>);' class='<%# "master" + (Container.DataItemIndex+1) %>' value="Open" />
<!--Label控制項給Server端讀取資訊用-->
<asp:Label ID="Label1" runat="server" Text='<%# Container.DataItemIndex+1 %>' ToolTip='<%# Eval("OrderID") %>' Visible="false" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</form>
</body>
</html>
.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.Threading;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
public partial class GridViewRowAddByRow : System.Web.UI.Page
{
string Conn_E = WebConfigurationManager.ConnectionStrings["NorthwindChineseConnectionString"].ConnectionString;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GV1Bind();
}
}
private void GV1Bind()
{
GridView1.DataSource = this.queryDataTable("Select * from Orders Order by OrderID ASC");
GridView1.DataBind();
}
int subRowCount = 0;
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType==DataControlRowType.DataRow)
{
TableCell cell = new TableCell();
cell.ColumnSpan = 3;
string index = (e.Row.FindControl("Label1") as Label).Text;
GridView gv = new GridView();
string OrderID = ((Label)e.Row.FindControl("Label1")).ToolTip;
gv.DataSource = this.queryDataTable("Select * from OrderDetails Where OrderID = '" + OrderID + "'");
gv.DataBind();
cell.Controls.Add(gv);
GridViewRow gvr = new GridViewRow(-1, 0, DataControlRowType.DataRow, DataControlRowState.Normal);
gvr.Cells.Add(cell);
gvr.CssClass = "master" + index + "_" + index;
gvr.Attributes["style"] = "display:none;";
subRowCount += 2;
GridView1.Controls[0].Controls.AddAt(subRowCount, gvr);
//GridView1.Controls[0]就是Table
//e.Row.Parent.Controls.AddAt(subRowCount,gvr);//這樣寫也可以
//e.Row.Parent就是Table
}
}
//撈出資料集
public DataTable queryDataTable(string sql)
{
using (SqlConnection conn = new SqlConnection(this.Conn_E))
{
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
DataSet ds = new DataSet();
da.Fill(ds);
return (ds.Tables.Count > 0) ? ds.Tables[0] : new DataTable();
}
}
}
執行效果:
展開前兩列
收合第二列
再收合第一列
分享個人在實務上處理Master-Detail功能
其實都是準備兩支程式(.Net Team和Java Team都是這樣做)
Step 1. A程式列出主表資料
Step 2. 在A程式的每筆資料都會有個超連結以類似master.aspx?PK=value型式傳PK值,到B程式
Step 3. B程式利用Request.QueryString["PK"]讀取PK值(Java則是request.getParameter(String name)),來呈現Detail明細資料
這樣不管程式維護或使用者操作都比較簡單單純
不然頂多弄成這篇文章一樣[ASP.net] 使用ListView自己寫Master-Detail功能 - 不用jQuery Ajax的手風摺琴版(用到UpdatePanel,客戶還會以為畫面當掉)
Master-Detail功能,個人都是力求畫面&程式碼儘量簡潔好維護,也方便使用者
但也曾經碰過這樣的主管,很要求重視Table表格要有展開功能說這樣才能一覽該主表資料所對應的明細資料…
後來我實現該功能了(加上排序、分頁再加上Ajax不刷新),系統到使用者測試階段,一些User向我反應(包含那位天才主管= =”),怎麼系統畫面凌亂,好難操作
呃,所以回過頭來看以上的程式碼,有沒有發現Code-Behind都在處理一堆UI邏輯?
Code-Behind會處理UI邏輯通常是手寫美工人員給的HTML 字串(跑for迴圈產生HTML Table或動態更換圖片等等)
至少程式碼也較好懂
個人是不喜歡在Code-Behind處理UI事情,Code-Behind光是客戶要求的商業邏輯就夠複雜了
若再加上以上那些程式碼,糾結混合的結果,只會自己讓自己痛苦
引用某網友所講「是你自己束縛自己,還是主管客戶限制了你」
結論:這種GridView包GridView功能還是看看就好,實務上使用還是敬而遠之吧。