none
GridView裡面,再次包含另一個小的GridView? RRS feed

解答

  • 這個問題,自從黃忠成老師公開範例之後,引起好多人迴響

    可見得大家都喜歡這個變化

    但我有另外一種想法:  GridView有其極限 -- 心裡有劍,手上無劍(不拘泥於手上既有的招式)
    http://www.dotblogs.com.tw/mis2000lab/archive/2009/02/25/gridview_limit_090225.aspx

    或許初學者可以想一想。

    是不是萬事都得靠 GridView才能做到呢?

    1. 是客戶(業主)限制你這麼做    .....那就沒辦法了  Orz
    2. 還是自己把自己限制住了?

    以開放的心,來學習這個功能。相信可以學到很多東西。釐清很多觀念。

    但如果是「不瘋魔、不成活」(語出陳凱哥電影 霸王別姬的台詞),
    對於初學者來說,那可能會是一種障礙


    我的ASP.NET教學網站 http://www.taconet.com.tw/mis2000_aspnet/
    • 已標示為解答 Lolota Lee 2009年3月23日 上午 07:09
    2009年3月23日 上午 06:36
  • 這個玩意挺有意思,我就另外在別的地方分享我的方法好了(至於在哪裡,一月下旬你就會知道了)。

     

    不過其實這是在玩 HTML 的魔術,HTML 本身再加上瀏覽器 DOM 的能力,本來就可以變出很多的把戲,只是看開發人員要怎麼處理而已。

     

    A. 開始。

     

    你可以先試試看:

     

    1. 在 GridView 中設一個 TemplateField,在裡面加上一個子 GridView。

    2. 在 RowDataBound 常式中填充子 GridView 的資料。

     

    這樣就有子母 GridView 的能力了。

     

    B. 利用動態生成在 GridView 的 Row 下方生成新的 GridViewRow,並動態生成子 GridView。

     

    再來,你就可以試看看在 GridView 中加新的 GridViewRow 的方法,用動態生成的方式來產生子 GridView。

     

    C. 將 GridView 放到 User Control。

     

    將子 GridView 放到一個 User Control 中,然後把 B 步驟的生成 GridView 的程式替換成載入 User Control。

    到了這一步,你的 GridView 看起來應該就很像黃老師的 GridView 了,只欠缺一個列縮放。

     

    D. 實作列縮放。

     

    將列縮放功能實作到主 GridView 中,就可以重現黃老師的 GridView 配置了。

    列縮放只是設定 GridViewRow 的 Visible 屬性而已。

    2008年1月9日 上午 09:35
    版主
  •  

    To A.W.

     

        抱歉造成你的困擾,這篇文章其實少了一大段前置的說明,

    原始文章長達100多頁,充份說明了GridView控件的物件佈局,

    不過我想這些資訊網路上應該已經很多了,所以就沒刊上此部份.

    於此附上較詳細的程式註解,希望能幫助你釐清一些疑惑.

    完整的Master-Detail GridView Part 2範例可由下列連結下載.

    http://www.dreams.idv.tw/~code6421/files/GridView2.zip

    PS: MSDN中的連結下載得到的是那個擁有明細分頁功能效果的例子.

     

    程式碼區塊

    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
    {
        //用來記錄目前已經展開的列之鍵值,以此例來說,即是OrderID欄位值,
        //此List會存在ViewState中,所以當PostBack發生時,
        //我們可以在GridView1_RowCreated函式中,用此List來判斷目前是否有列展開,

        //以此維持控件狀態
        private List<int> _collaspedRows = new List<int>();

        //判斷目前是否有列展開的函式.
        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,注意,此處傳入的是展開列,

                //我們暫時將RowIndex設成與展開列相同.
                GridViewRow row = new GridViewRow(gridRow.RowIndex, -1,

                     DataControlRowType.DataRow, DataControlRowState.Normal);
                //架構上,GridViewRow是一個Table,此處於此Table中建立兩個Cell.
                //加入第一個Cell可以做出空一格的效果,不會讓新建的GridViewRow

                //與原來的列起始位置

                //相同,營造出新建的GridViewRow看起來是主列的子表
                TableCell cell = new TableCell();
                row.Cells.Add(cell);
                //加入第二個Cell,此Cell是用來顯示DetailGrid.ascx,請注意,

                //Colspan設為Columns.Count-1,也就是合併所有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);
                //將新建的GridViewRow加到GridView中,注意!此列將插入於展開列的下方

                //(+2 的原因是包含Header Row)
                GridView1.Controls[0].Controls.AddAt(gridRow.RowIndex + 2, row);
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.Pager)
            {
                //開始建立Pager,代表所有DataRow均已佈局完成,

                //現在可以開始建立展開列的GridViewRow了.
                for (int i = 0; i < GridView1.Rows.Count; i++)
                        CreateDetailRow(GridView1.Rows[i]);
            }
        }

        //將已展開列的資訊由ViewState讀回.
        protected override void LoadViewState(object savedState)
        {
            Pair state = (Pair)savedState;
            base.LoadViewState(state.First);
            _collaspedRows = (List<int>)state.Second;
        }

        //將已展開列的資訊存入ViewState中.
        protected override object SaveViewState()
        {
            Pair state = new Pair(base.SaveViewState(), _collaspedRows);
            return state;
        }
       
        protected void LinkButton1_Click(object sender, EventArgs e)
        {
            LinkButton btn = (LinkButton)sender;       
            int key = int.Parse(btn.CommandArgument); 

             //我們在Details這個LinkButton中

             //設定CommandArgument與OrderID欄位值繫結
            if (_collaspedRows.Contains(key)) //列已展開,縮起
            {
                _collaspedRows.Remove(key); //清空已展開列資訊
                GridView1.DataBind(); //Re-DataBind,

                                      //要求GridView重新建立所有列,

                                      //此次由於無列被展開,所以不會出現Details.
            }
            else
            {
                _collaspedRows.Clear(); //若有列展開,清空.
                _collaspedRows.Add(key); //將欲展開列的鍵值存到已展開列儲存體中.
                GridView1.DataBind(); //Re-DataBind,接下來

                                      //RowCreated會被呼叫,Details會被建立.
            }
        }
        protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
        {
            _collaspedRows.Clear();
        }

        //幾個關鍵點.
        //1. GridView的每一列都是一個GridViewRow物件,

        //   每個GridViewRow物件都是一個Table.
        //
        //2. DetailsGrid.ascx嵌入何處?
        //   一個新建的GridViewRow中,此GridViewRow擁有兩個TableCell,

        //   第一個可以讓此GridViewRow
        //   前面空一格,第二個即是放入DetailsGrid.ascx.
        //3. 新建的GridViewRow何時加入GridView?
        //   我們無法於展開列建立時加立即加入新建的GridViewRow,

        //   這會破壞原先GridViewRow的佈局,
        //   所以直至PagerRow建立時才建立該GridViewRow並加入,

        //   當然,你也可以以RowIndex來判斷
        //4. 為何要儲存已展開列的資訊至ViewState?
        //   若不這麼做,一旦有PostBack發生時,

        //   我們無從得知那個列已被展開,因此也無法重建該列,
        //   結果便是,PostBack發生後,GridView回到所有列未展開狀態,

        //   若PostBack是由DetailsGrid.ascx
        //   所觸發,便意味著DetailsGrid.ascx將完全無法操作,

        //   此點與Dynamic Load Control情況相同.
        //5. 會使用List<int>來儲存已展開列資訊的原因是,

        //   此例原本是支援多列同時展開的設計,
        //   後來我覺得亂,所以就移除該設計了,

        //   不過還是維持使用List來儲存已展開列資訊就是了.
        //
        //---------------------------------------------------------------------------------
        //模擬.
        //1. 使用者點選某列的Details 連結
        //2. LinkButton1_Click被呼叫,呼叫GridView1.DataBind

        //   會引發GridView1_RowCreated被呼叫.
        //3. GridView於每建立一列都會呼叫一次GridView1_RowCreated,

        //   我們等到建立的列是Pager時.
        //4. 當建立的列是Pager時,建立GridViewRow,插入兩個Cell,

        //   插入DetailsGrid.ascx至第二個Cell.
        //5. 儲存ViewState,將已展開列的鍵值(OrderID)存入ViewState中.
        //6. 使用者點選DetailsGrid.ascx中的編輯連結.
        //7. PostBack發生.
        //8. LoadViewState被呼叫,由ViewState讀回已展開列的鍵值資訊.
        //9. GirdView開始建立所有列,GridView1_RowCreated會被呼叫多次
        //10. 當建立的列是Pager時,建立GridViewRow,插入兩個Cell,

        //    插入DetailsGrid.ascx至第二個Cell.
    }

     

     

     

    2008年1月10日 上午 12:13
  • 下面是我修改的版本,可能是我考慮不周吧。功能可能有缺

    寫出來的程式比較沒有黃老師那麼詳細

     

    但執行起來,好像沒有大錯

     

    但盡可能少寫程式,應該比較適合初學的朋友來參考

    非常感謝小朱&黃老師在上面的解釋。

    這功能我也肖想很久了

     

    我主要是參考黃老師 CreatDetailRow () 這一段來改的

     

    我的使用者控制項(名為:test_UserControl_1.ascx)如下,

    少了資料(值)傳遞(黃老師的 ViewState)的那部份

    只專注在「大腸包小腸,大gridview 包 小gridview」的部份

     

           

    程式碼區塊
    <!-- 註解:使用者控制項(名為:test_UserControl_1.ascx) -->
     
    <asp:GridView ID="GridView2" runat="server" DataKeyNames="id" DataSourceID="SqlDataSource2">
             ....省略....是用VS 2005做好的GridView,沒啥特別。
    </asp:GridView>        
            
            
            
    <asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:testConnectionString %>"
    SelectCommand="SELECT * FROM [test_talk] WHERE ([test_id] = @test_id)">
                <SelectParameters>
                    <asp:ControlParameter ControlID="GridView1" Name="test_id"
                    PropertyName="SelectedValue" Type="Int32" />
                </SelectParameters>
    </asp:SqlDataSource>

     

     

     

    主畫面也很簡單,只有GridVeiw1 & SqlDataSource1 各一個。

     

    後置程式碼的部份

       1. 使用者每次點選一列,就會出現 Detail列 (也就是所謂的 Master-Detail)

        2. 透過每次GridView1.DataBind() 重新作 Detail列

        就沒有黃老師的「消除 列 / .Clear()」那一部份。我也不知道這樣是好是壞,但功能大多都有跑出來

     

    很感謝黃忠成老師的註解與分享

       

    程式碼區塊

    Protected Sub GridView1_PageIndexChanging(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewPageEventArgs) Handles GridView1.PageIndexChanging
            '==換頁的時後,將所有狀態都恢復原狀!==
            Session("myGridViewNum") = -1
            GridView1.SelectedIndex = -1

            GridView1.PageIndex = e.NewPageIndex
            GridView1.DataBind()
    End Sub

     

     

    Protected Sub GridView1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles GridView1.SelectedIndexChanged
            Session("myGridViewNum") = CInt(GridView1.SelectedIndex.ToString)

            GridView1.DataBind()  

    End Sub

     


    Protected Sub GridView1_RowCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowCreated

            If Session("myGridViewNum") >= 0 And e.Row.RowType = DataControlRowType.Pager Then
                '====Pager表示GridView已經完成最底下[分頁]功能的格子,整個GridView都已經完成了

                Dim myRow As New GridViewRow((Session("myGridViewNum") + 1), 0, DataControlRowType.DataRow, DataControlRowState.Normal)
                '==參考資料
    http://msdn2.microsoft.com/zh-tw/library/system.web.ui.webcontrols.gridviewrow.gridviewrow(VS.80).aspx


     

                Dim headerCell_0 As New TableCell
                myRow.Cells.Add(headerCell_0)  '==新增一個 TableCell,避開第一格的「按鈕」

               

                Dim headerCell_1 As New TableCell
                headerCell_1.ColumnSpan = CInt(GridView1.Columns.Count)
                '==合併所有格子變成一列,來呈現另一個 GridView2 (使用者控制項,User Control)


     

                If Page.IsPostBack Then 

                '==避免第一次載入網頁,就看見 GridView2的錯誤訊息(找不到資料)
                    Dim myuc As New UserControl()
                    myuc = LoadControl("test_UserControl_1.ascx")
                    headerCell_1.Controls.Add(myuc) '==在這一格裡面,加入 GridView2 [使用者控制項,User Control]

                    myRow.Cells.Add(headerCell_1) '==新增一個 TableCell


                    myRow.Visible = True

                    GridView1.Controls(0).Controls.AddAt((Session("myGridViewNum") + 2), myRow)
                    '==加入 GridView1裡面,加在"展開那一列"的下方。因為包含表頭那一列,所以要加二。
                End If
            End If

    End Sub

     

    不好意思  情急之下  隨手用了 Session()實在欠考慮

     

    上面紅色的那段程式  找了微軟的MSDN看過了,

    但我還是攪不清楚  能否有人幫忙解說一下呢?


    廣告一下,

    相關的一些範例,我都收錄在書裡面了。新書上市-- ASP.NET專題實務(文魁出版)

    光是GridView的範例,我就寫了五章,約250頁。

    2008年1月10日 上午 08:04
  • 我的版本沒有用 User Control,也沒有像黃老師 override LoadViewState() 和 SaveViewState(),不過也沒有超過 100 行

     

     

    先透露一部份關鍵好了:

    程式碼區塊

     

    protected void MasterView_RowCreated(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.Footer)
            this.CreateCollapseRow();
    }

    private void CreateCollapseRow()
    {
        foreach (GridViewRow row in MasterView.Rows)
        {
            if (row.RowIndex == this.fireCommandRowIndex) // compare selected index.
            {
                MasterView.SelectedIndex = this.fireCommandRowIndex;

     

                GridViewRow r = new GridViewRow(row.RowIndex, -1,

                     DataControlRowType.DataRow, DataControlRowState.Normal);


                StringWriter sw = new StringWriter();
                HtmlTextWriter hw = new HtmlTextWriter(sw);

     

                r.Cells.Add(new TableCell());
                r.Cells.Add(new TableCell());

                r.Cells[1].ColumnSpan = MasterView.Columns.Count - 1;

     

                this.dsDetail.SelectParameters["orderID"].DefaultValue =

                     row.Cells[1].Text;

     

                this.DetailView.Visible = true;
                this.DetailView.DataSource =

                     (this.dsDetail.Select(new DataSourceSelectArguments()) as DataView);
                this.DetailView.DataBind();
                this.DetailView.RenderControl(hw);
                this.DetailView.Visible = false;

     

                r.Cells[1].Text = sw.ToString();
                sw.Close();

     

                if (MasterView.AllowPaging &&

                    MasterView.PagerSettings.Position == PagerPosition.TopAndBottom)
                    MasterView.Controls[0].Controls.AddAt(row.RowIndex + 3, r);
                else
                    MasterView.Controls[0].Controls.AddAt(row.RowIndex + 2, r);
            }
        }
    }

     

     

    其他的就暫時不能透露 ...,要等一月下旬過後。

    2008年1月11日 上午 10:35
    版主
  • 我也寫了個相關文章供參考

     

    十幾行程式碼搞定 Master-Detail GridView(內含子 GridView)
    http://www.dotblogs.com.tw/jeff377/archive/2008/06/21/4348.aspx

     

    2008年6月20日 下午 05:37
  •  

    小喵針對這個問題,小喵分享小喵的做法

    請參考以下這篇文章

     

    GridView展現Master-Detail的幾種方式(包含動態錄影教學)

    2008年6月21日 上午 01:14
    版主

所有回覆

  • 這個玩意挺有意思,我就另外在別的地方分享我的方法好了(至於在哪裡,一月下旬你就會知道了)。

     

    不過其實這是在玩 HTML 的魔術,HTML 本身再加上瀏覽器 DOM 的能力,本來就可以變出很多的把戲,只是看開發人員要怎麼處理而已。

     

    A. 開始。

     

    你可以先試試看:

     

    1. 在 GridView 中設一個 TemplateField,在裡面加上一個子 GridView。

    2. 在 RowDataBound 常式中填充子 GridView 的資料。

     

    這樣就有子母 GridView 的能力了。

     

    B. 利用動態生成在 GridView 的 Row 下方生成新的 GridViewRow,並動態生成子 GridView。

     

    再來,你就可以試看看在 GridView 中加新的 GridViewRow 的方法,用動態生成的方式來產生子 GridView。

     

    C. 將 GridView 放到 User Control。

     

    將子 GridView 放到一個 User Control 中,然後把 B 步驟的生成 GridView 的程式替換成載入 User Control。

    到了這一步,你的 GridView 看起來應該就很像黃老師的 GridView 了,只欠缺一個列縮放。

     

    D. 實作列縮放。

     

    將列縮放功能實作到主 GridView 中,就可以重現黃老師的 GridView 配置了。

    列縮放只是設定 GridViewRow 的 Visible 屬性而已。

    2008年1月9日 上午 09:35
    版主
  • 恆逸資訊的 .net magazine 網站

     http://www.netmag.com.tw/

     

    在 2006 年 3 月就有一篇文章

    「GridView 內嵌 DetailView 的設計技巧」

    裡就有相似應用,

    小弟也曾實際想在專案裡用,但最後還是放棄了,

     

    GridView 裡嵌 DetailsView 顯示資料,如恆逸網站該文說的可以,

    但程式碼會變很複雜,FindControl 就搞死人,

    可顯示,但程式碼一坨亂。

     

    且裡層的 DetailsView 輸入的資料,要修改、寫入資料庫會有問題,

    這點比顯示資料還麻煩,小弟更新功能沒成功也就玩不下去了。

     

    考慮到系統以後維護的問題,就不想玩了。

     

    不過黃大師該文有粉多不錯的極意 idea :

    http://blog.csdn.net/Code6421

     

     

    2008年1月9日 下午 12:54
  •  

    To A.W.

     

        抱歉造成你的困擾,這篇文章其實少了一大段前置的說明,

    原始文章長達100多頁,充份說明了GridView控件的物件佈局,

    不過我想這些資訊網路上應該已經很多了,所以就沒刊上此部份.

    於此附上較詳細的程式註解,希望能幫助你釐清一些疑惑.

    完整的Master-Detail GridView Part 2範例可由下列連結下載.

    http://www.dreams.idv.tw/~code6421/files/GridView2.zip

    PS: MSDN中的連結下載得到的是那個擁有明細分頁功能效果的例子.

     

    程式碼區塊

    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
    {
        //用來記錄目前已經展開的列之鍵值,以此例來說,即是OrderID欄位值,
        //此List會存在ViewState中,所以當PostBack發生時,
        //我們可以在GridView1_RowCreated函式中,用此List來判斷目前是否有列展開,

        //以此維持控件狀態
        private List<int> _collaspedRows = new List<int>();

        //判斷目前是否有列展開的函式.
        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,注意,此處傳入的是展開列,

                //我們暫時將RowIndex設成與展開列相同.
                GridViewRow row = new GridViewRow(gridRow.RowIndex, -1,

                     DataControlRowType.DataRow, DataControlRowState.Normal);
                //架構上,GridViewRow是一個Table,此處於此Table中建立兩個Cell.
                //加入第一個Cell可以做出空一格的效果,不會讓新建的GridViewRow

                //與原來的列起始位置

                //相同,營造出新建的GridViewRow看起來是主列的子表
                TableCell cell = new TableCell();
                row.Cells.Add(cell);
                //加入第二個Cell,此Cell是用來顯示DetailGrid.ascx,請注意,

                //Colspan設為Columns.Count-1,也就是合併所有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);
                //將新建的GridViewRow加到GridView中,注意!此列將插入於展開列的下方

                //(+2 的原因是包含Header Row)
                GridView1.Controls[0].Controls.AddAt(gridRow.RowIndex + 2, row);
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.Pager)
            {
                //開始建立Pager,代表所有DataRow均已佈局完成,

                //現在可以開始建立展開列的GridViewRow了.
                for (int i = 0; i < GridView1.Rows.Count; i++)
                        CreateDetailRow(GridView1.Rows[i]);
            }
        }

        //將已展開列的資訊由ViewState讀回.
        protected override void LoadViewState(object savedState)
        {
            Pair state = (Pair)savedState;
            base.LoadViewState(state.First);
            _collaspedRows = (List<int>)state.Second;
        }

        //將已展開列的資訊存入ViewState中.
        protected override object SaveViewState()
        {
            Pair state = new Pair(base.SaveViewState(), _collaspedRows);
            return state;
        }
       
        protected void LinkButton1_Click(object sender, EventArgs e)
        {
            LinkButton btn = (LinkButton)sender;       
            int key = int.Parse(btn.CommandArgument); 

             //我們在Details這個LinkButton中

             //設定CommandArgument與OrderID欄位值繫結
            if (_collaspedRows.Contains(key)) //列已展開,縮起
            {
                _collaspedRows.Remove(key); //清空已展開列資訊
                GridView1.DataBind(); //Re-DataBind,

                                      //要求GridView重新建立所有列,

                                      //此次由於無列被展開,所以不會出現Details.
            }
            else
            {
                _collaspedRows.Clear(); //若有列展開,清空.
                _collaspedRows.Add(key); //將欲展開列的鍵值存到已展開列儲存體中.
                GridView1.DataBind(); //Re-DataBind,接下來

                                      //RowCreated會被呼叫,Details會被建立.
            }
        }
        protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
        {
            _collaspedRows.Clear();
        }

        //幾個關鍵點.
        //1. GridView的每一列都是一個GridViewRow物件,

        //   每個GridViewRow物件都是一個Table.
        //
        //2. DetailsGrid.ascx嵌入何處?
        //   一個新建的GridViewRow中,此GridViewRow擁有兩個TableCell,

        //   第一個可以讓此GridViewRow
        //   前面空一格,第二個即是放入DetailsGrid.ascx.
        //3. 新建的GridViewRow何時加入GridView?
        //   我們無法於展開列建立時加立即加入新建的GridViewRow,

        //   這會破壞原先GridViewRow的佈局,
        //   所以直至PagerRow建立時才建立該GridViewRow並加入,

        //   當然,你也可以以RowIndex來判斷
        //4. 為何要儲存已展開列的資訊至ViewState?
        //   若不這麼做,一旦有PostBack發生時,

        //   我們無從得知那個列已被展開,因此也無法重建該列,
        //   結果便是,PostBack發生後,GridView回到所有列未展開狀態,

        //   若PostBack是由DetailsGrid.ascx
        //   所觸發,便意味著DetailsGrid.ascx將完全無法操作,

        //   此點與Dynamic Load Control情況相同.
        //5. 會使用List<int>來儲存已展開列資訊的原因是,

        //   此例原本是支援多列同時展開的設計,
        //   後來我覺得亂,所以就移除該設計了,

        //   不過還是維持使用List來儲存已展開列資訊就是了.
        //
        //---------------------------------------------------------------------------------
        //模擬.
        //1. 使用者點選某列的Details 連結
        //2. LinkButton1_Click被呼叫,呼叫GridView1.DataBind

        //   會引發GridView1_RowCreated被呼叫.
        //3. GridView於每建立一列都會呼叫一次GridView1_RowCreated,

        //   我們等到建立的列是Pager時.
        //4. 當建立的列是Pager時,建立GridViewRow,插入兩個Cell,

        //   插入DetailsGrid.ascx至第二個Cell.
        //5. 儲存ViewState,將已展開列的鍵值(OrderID)存入ViewState中.
        //6. 使用者點選DetailsGrid.ascx中的編輯連結.
        //7. PostBack發生.
        //8. LoadViewState被呼叫,由ViewState讀回已展開列的鍵值資訊.
        //9. GirdView開始建立所有列,GridView1_RowCreated會被呼叫多次
        //10. 當建立的列是Pager時,建立GridViewRow,插入兩個Cell,

        //    插入DetailsGrid.ascx至第二個Cell.
    }

     

     

     

    2008年1月10日 上午 12:13
  •  

    To 小朱大大,

     

           感謝你的說明,一針見血,清楚得很  ^_^

      也提醒了我還有另一種更簡單的手法(以Visible來玩,可以避開ViewState/PostBack/Control Creating等處理),

      我對於控件的建立有莫名的潔癖,思考邏輯永遠從Dynamic Creating Control開始跑.......哈

    To 效能狂,

     

          的確,將GridView用到這步田地,不可避免的必須更小心Control State的處理,

      不過我想只要充份了解ViewState/PostBack間的關係,要維持Control的運作不會是太大問題.

      PS: 呵,我是很大隻的貓啦,連大獅都稱不上 ;-)

    2008年1月10日 上午 01:15
  •  code6421 寫信:

    To 小朱大大,

     

           感謝你的說明,一針見血,清楚得很  ^_^

      也提醒了我還有另一種更簡單的手法(以Visible來玩,可以避開ViewState/PostBack/Control Creating等處理),

      我對於控件的建立有莫名的潔癖,思考邏輯永遠從Dynamic Creating Control開始跑.......哈

     

    黃老師您客氣了。

    我也拜讀過您的多本著作,對您使用與發展元件的功力深感佩服。

    2008年1月10日 上午 03:10
    版主
  • 下面是我修改的版本,可能是我考慮不周吧。功能可能有缺

    寫出來的程式比較沒有黃老師那麼詳細

     

    但執行起來,好像沒有大錯

     

    但盡可能少寫程式,應該比較適合初學的朋友來參考

    非常感謝小朱&黃老師在上面的解釋。

    這功能我也肖想很久了

     

    我主要是參考黃老師 CreatDetailRow () 這一段來改的

     

    我的使用者控制項(名為:test_UserControl_1.ascx)如下,

    少了資料(值)傳遞(黃老師的 ViewState)的那部份

    只專注在「大腸包小腸,大gridview 包 小gridview」的部份

     

           

    程式碼區塊
    <!-- 註解:使用者控制項(名為:test_UserControl_1.ascx) -->
     
    <asp:GridView ID="GridView2" runat="server" DataKeyNames="id" DataSourceID="SqlDataSource2">
             ....省略....是用VS 2005做好的GridView,沒啥特別。
    </asp:GridView>        
            
            
            
    <asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:testConnectionString %>"
    SelectCommand="SELECT * FROM [test_talk] WHERE ([test_id] = @test_id)">
                <SelectParameters>
                    <asp:ControlParameter ControlID="GridView1" Name="test_id"
                    PropertyName="SelectedValue" Type="Int32" />
                </SelectParameters>
    </asp:SqlDataSource>

     

     

     

    主畫面也很簡單,只有GridVeiw1 & SqlDataSource1 各一個。

     

    後置程式碼的部份

       1. 使用者每次點選一列,就會出現 Detail列 (也就是所謂的 Master-Detail)

        2. 透過每次GridView1.DataBind() 重新作 Detail列

        就沒有黃老師的「消除 列 / .Clear()」那一部份。我也不知道這樣是好是壞,但功能大多都有跑出來

     

    很感謝黃忠成老師的註解與分享

       

    程式碼區塊

    Protected Sub GridView1_PageIndexChanging(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewPageEventArgs) Handles GridView1.PageIndexChanging
            '==換頁的時後,將所有狀態都恢復原狀!==
            Session("myGridViewNum") = -1
            GridView1.SelectedIndex = -1

            GridView1.PageIndex = e.NewPageIndex
            GridView1.DataBind()
    End Sub

     

     

    Protected Sub GridView1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles GridView1.SelectedIndexChanged
            Session("myGridViewNum") = CInt(GridView1.SelectedIndex.ToString)

            GridView1.DataBind()  

    End Sub

     


    Protected Sub GridView1_RowCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowCreated

            If Session("myGridViewNum") >= 0 And e.Row.RowType = DataControlRowType.Pager Then
                '====Pager表示GridView已經完成最底下[分頁]功能的格子,整個GridView都已經完成了

                Dim myRow As New GridViewRow((Session("myGridViewNum") + 1), 0, DataControlRowType.DataRow, DataControlRowState.Normal)
                '==參考資料
    http://msdn2.microsoft.com/zh-tw/library/system.web.ui.webcontrols.gridviewrow.gridviewrow(VS.80).aspx


     

                Dim headerCell_0 As New TableCell
                myRow.Cells.Add(headerCell_0)  '==新增一個 TableCell,避開第一格的「按鈕」

               

                Dim headerCell_1 As New TableCell
                headerCell_1.ColumnSpan = CInt(GridView1.Columns.Count)
                '==合併所有格子變成一列,來呈現另一個 GridView2 (使用者控制項,User Control)


     

                If Page.IsPostBack Then 

                '==避免第一次載入網頁,就看見 GridView2的錯誤訊息(找不到資料)
                    Dim myuc As New UserControl()
                    myuc = LoadControl("test_UserControl_1.ascx")
                    headerCell_1.Controls.Add(myuc) '==在這一格裡面,加入 GridView2 [使用者控制項,User Control]

                    myRow.Cells.Add(headerCell_1) '==新增一個 TableCell


                    myRow.Visible = True

                    GridView1.Controls(0).Controls.AddAt((Session("myGridViewNum") + 2), myRow)
                    '==加入 GridView1裡面,加在"展開那一列"的下方。因為包含表頭那一列,所以要加二。
                End If
            End If

    End Sub

     

    不好意思  情急之下  隨手用了 Session()實在欠考慮

     

    上面紅色的那段程式  找了微軟的MSDN看過了,

    但我還是攪不清楚  能否有人幫忙解說一下呢?


    廣告一下,

    相關的一些範例,我都收錄在書裡面了。新書上市-- ASP.NET專題實務(文魁出版)

    光是GridView的範例,我就寫了五章,約250頁。

    2008年1月10日 上午 08:04
  • 我的版本沒有用 User Control,也沒有像黃老師 override LoadViewState() 和 SaveViewState(),不過也沒有超過 100 行

     

     

    先透露一部份關鍵好了:

    程式碼區塊

     

    protected void MasterView_RowCreated(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.Footer)
            this.CreateCollapseRow();
    }

    private void CreateCollapseRow()
    {
        foreach (GridViewRow row in MasterView.Rows)
        {
            if (row.RowIndex == this.fireCommandRowIndex) // compare selected index.
            {
                MasterView.SelectedIndex = this.fireCommandRowIndex;

     

                GridViewRow r = new GridViewRow(row.RowIndex, -1,

                     DataControlRowType.DataRow, DataControlRowState.Normal);


                StringWriter sw = new StringWriter();
                HtmlTextWriter hw = new HtmlTextWriter(sw);

     

                r.Cells.Add(new TableCell());
                r.Cells.Add(new TableCell());

                r.Cells[1].ColumnSpan = MasterView.Columns.Count - 1;

     

                this.dsDetail.SelectParameters["orderID"].DefaultValue =

                     row.Cells[1].Text;

     

                this.DetailView.Visible = true;
                this.DetailView.DataSource =

                     (this.dsDetail.Select(new DataSourceSelectArguments()) as DataView);
                this.DetailView.DataBind();
                this.DetailView.RenderControl(hw);
                this.DetailView.Visible = false;

     

                r.Cells[1].Text = sw.ToString();
                sw.Close();

     

                if (MasterView.AllowPaging &&

                    MasterView.PagerSettings.Position == PagerPosition.TopAndBottom)
                    MasterView.Controls[0].Controls.AddAt(row.RowIndex + 3, r);
                else
                    MasterView.Controls[0].Controls.AddAt(row.RowIndex + 2, r);
            }
        }
    }

     

     

    其他的就暫時不能透露 ...,要等一月下旬過後。

    2008年1月11日 上午 10:35
    版主
  • To MIS2000 Lab. 前輩:

    是否可以讓我參考您的以vb寫的方式呢?

    我參考黃老師的語法,似乎不太明白其用法

    謝謝

    2008年6月4日 下午 03:06
  • 請問一下 

    如何作到子gridview更新完成後 母gridview也能同時更新內容

     

    2008年6月17日 上午 05:16
  • For  安德森:

    我的程式碼(VB語法)完全公開囉,沒有藏私。上面的回應,就是完整的程式碼了。

     

     

    For blue7016:

    當您的子GridView更新完成後,

    讓母GridView重新作一次 .DataBind(),應該就能達到您想要的結果。

    (Sorry....我沒有實作過,上面的狀況下,子GridView能否作編輯與更新)

     

     

    2008年6月17日 上午 05:23
  • 子 GridView 是用 RenderControl 方式將子 GridView 的 HTML 碼直接設給 Cell.Text,所以子 GridView 無法支援編輯的。
    2008年6月17日 上午 06:08
  • 我也寫了個相關文章供參考

     

    十幾行程式碼搞定 Master-Detail GridView(內含子 GridView)
    http://www.dotblogs.com.tw/jeff377/archive/2008/06/21/4348.aspx

     

    2008年6月20日 下午 05:37
  •  

    小喵針對這個問題,小喵分享小喵的做法

    請參考以下這篇文章

     

    GridView展現Master-Detail的幾種方式(包含動態錄影教學)

    2008年6月21日 上午 01:14
    版主
  • 請教一下

    我試著將黃老師的範例改成以VB撰寫

    但在CreatDetailRow中的

    ((DetailsGrid)c).OrderID = (int)GridView1.DataKeys[gridRow.RowIndex].Value;
    這一行卻遇到問題

    DetailGrid應該是用loadcontrol傳入的沒錯吧

    但在用型別轉換時卻發生"未定義"的錯誤而不能執行

    我用DirectCast跟ctype都是一樣

    請問這該如何解決??

    2008年6月30日 上午 03:36
  •  

    我有試著用小朱大大和黃忠成老師的寫法去做,

    不過都會一直出現Index超出索引值的錯誤,

    加上老師們寫得太高深,不知要如何下手去debug,

    所以改採用 MIS2000 Lab.大大的寫法,

    不過我把存session的地方用viewstate去做,

    還有這個判斷式

    If Session("myGridViewNum") >= 0 And e.Row.RowType = DataControlRowType.Pager

    多再一個是否為null的判斷,
    雖然只能做打開子GridView的動作,無法縮起來,
    但跑起來還滿順利的,

    感謝各位大大們的指導^^

     

    2008年7月8日 上午 07:56
  • 這個是我的 Collapse-GridView 的最終版本:

    http://www.flag.com.tw/book/cento-5105.asp?bokno=FT470&id=437

    2008年7月8日 上午 08:22
    版主
  • 至各位大大,我用了黃大大的GRIDVIEW主、詳表在其中GridView1.Controls[0].Controls.AddAt(gridRow.RowIndex + 2, row);運行不過去報INDEX超出了範圍,
    把2改成1就可以,在要展開列的上面。這個是不是在GRIDVIEW在初始化行時沒有要展開列下壹行所造成的啊!

    2008年8月8日 上午 03:04
  •  

    請問一下
    黃老師的範例在CreatDetailRow中的

    Control c = LoadControl("DetailsGrid.ascx");  
    ((DetailsGrid)c).OrderID = (int)GridView1.DataKeys[gridRow.RowIndex].Value;  
     

    ((DetailsGrid)c).OrderID是將讀進來DetailsGrid.ascx的 Control c 再強制轉型回 DetailsGrid.ascx 嗎?

    DetailsGrid.ascx 使用者控制項並非namespec或clsaa

    在用型別轉換時發生"未定義"的錯誤而不能執行

    請問這該如何解決??


    Dream.Coming
    2009年3月6日 上午 06:23
  • Dream.Coming 表示:

     

    請問一下
    黃老師的範例在CreatDetailRow中的

    Control c = LoadControl("DetailsGrid.ascx");  
    ((DetailsGrid)c).OrderID = (int)GridView1.DataKeys[gridRow.RowIndex].Value;  
     

    ((DetailsGrid)c).OrderID是將讀進來DetailsGrid.ascx的 Control c 再強制轉型回 DetailsGrid.ascx 嗎?

    DetailsGrid.ascx 使用者控制項並非namespec或clsaa

    在用型別轉換時發生"未定義"的錯誤而不能執行

    請問這該如何解決??


    Dream.Coming



    哪位前輩高手請高抬貴手解答一下.....
    感恩.....
    Dream.Coming
    2009年3月9日 上午 06:25
  • 人不知 表示:

    至各位大大,我用了黃大大的GRIDVIEW主、詳表在其中GridView1.Controls[0].Controls.AddAt(gridRow.RowIndex + 2, row);運行不過去報INDEX超出了範圍,
    把2改成1就可以,在要展開列的上面。這個是不是在GRIDVIEW在初始化行時沒有要展開列下壹行所造成的啊!

    這一段程式的意思,

    你要展開的那一列(放置小的Gridview,你所謂的詳表)放在這兩列底下

    是包含了 GridView的表頭欄位(Hrader) ,然後再加上「要展開的那一列」
    所以才會加二。





    我的ASP.NET教學網站 http://www.taconet.com.tw/mis2000_aspnet/
    2009年3月23日 上午 06:32
  • 這個問題,自從黃忠成老師公開範例之後,引起好多人迴響

    可見得大家都喜歡這個變化

    但我有另外一種想法:  GridView有其極限 -- 心裡有劍,手上無劍(不拘泥於手上既有的招式)
    http://www.dotblogs.com.tw/mis2000lab/archive/2009/02/25/gridview_limit_090225.aspx

    或許初學者可以想一想。

    是不是萬事都得靠 GridView才能做到呢?

    1. 是客戶(業主)限制你這麼做    .....那就沒辦法了  Orz
    2. 還是自己把自己限制住了?

    以開放的心,來學習這個功能。相信可以學到很多東西。釐清很多觀念。

    但如果是「不瘋魔、不成活」(語出陳凱哥電影 霸王別姬的台詞),
    對於初學者來說,那可能會是一種障礙


    我的ASP.NET教學網站 http://www.taconet.com.tw/mis2000_aspnet/
    • 已標示為解答 Lolota Lee 2009年3月23日 上午 07:09
    2009年3月23日 上午 06:36
  • 人不知 表示:

     

    至各位大大,我用了黃大大的GRIDVIEW主、詳表在其中GridView1.Controls[0].Controls.AddAt(gridRow.RowIndex + 2, row);運行不過去報INDEX超出了範圍,
    把2改成1就可以,在要展開列的上面。這個是不是在GRIDVIEW在初始化行時沒有要展開列下壹行所造成的啊!

     

    這一段程式的意思,

    你要展開的那一列(放置小的Gridview,你所謂的詳表)放在這兩列底下

    是包含了 GridView的表頭欄位(Hrader) ,然後再加上「要展開的那一列」
    所以才會加二。





    我的ASP.NET教學網站 http://www.taconet.com.tw/mis2000_aspnet/
    呵呵~原來是這樣呀~
    感恩....

    Dream.Coming
    2009年5月18日 上午 09:46
  • 關於這個範例,我參考了微軟MSDN的文章,
    有找到另外一個更簡單的版本(有AJAX功能)

    文章的解說與範例如下,希望對各位有幫助:

    GridView密技#6---[習題]大腸包小腸 / 巢狀GridView -- Part II(AJAX版)

    網址 http://www.dotblogs.com.tw/mis2000lab/archive/2009/11/18/gridview_updatepanel_ajax_20091118.aspx
    我的書 與 ASP.NET教學網站 http://www.dotblogs.com.tw/mis2000lab/
    2009年11月24日 上午 04:39