2011年9月16日 星期五

讓 GridView 的 checkbox 狀態於分頁後還能保留


當 GridView 為每一筆資料列擺上 checkbox 時,別以為這樣對 user 很貼心喔,其實是夢靨的開始。 因為聰明的 user 在換頁後很快就會發現,前一頁有勾選的資料,在換頁之後,勾選狀態就不見了。原本你期待的是讚美與鼓勵,一夕間就變成 bug 與缺失了。想到下班後還要繼續加班改程式,所言至此 ,就不禁潸然流下男兒淚了...

不過,寫程式的最高心法就是:Copy Right (Copy is right,拷貝是對的)。往好處想,至少下次再遇到同樣的問題時,就可以不用再這麼辛苦從無到有了。如此累積下去,幾年後,每個人肯定都會身懷絕技。於是將這次解決的方法寫下來,日後再遇到同樣問題,有時間的話可以再去進一步最佳化;如果沒時間,至少也不會開天窗。 

我參考了網路上的文章,針對這問題提出兩種解決的版本,一種是從後端(server side)來處理,另一種則是從前端(client side)來處理。兩者的差別,在於勾選後是否會觸發 postback。當然,個人是比較偏好 client side 來處理,主要是因為程式碼比較簡潔、程式執行比較快速。

 ------ server side -------
說明:
將已勾選資訊存放在 List<string> lcb ,並將它透過 viewstate 來包裝。在 gridview 的 RowDataBound 事件,針對每個 checkbox 去註冊 GetPostBackEventReference(),讓 checkbox 一被勾選就會觸發 postback,同時將唯一可識別的資訊當作 postback 的參數。並於 Page_Load 攔截所 有 postback 事件,判斷是否是 checkbox 所發出,如果是,就更新 List<string> lcb 與 gridview 上 checkbox 的勾選與否資訊。

 .aspx
        <asp:GridView ID="gv" runat="server" AutoGenerateColumns="False" 
            EnableModelValidation="True" AllowPaging="True" DataSourceID="dsData" 
            onrowdatabound="gv_RowDataBound" PageSize="5">
            <Columns>
                <asp:TemplateField>
                    <ItemTemplate>
                        <asp:CheckBox ID="cb" runat="server" />
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:BoundField DataField="id" HeaderText="ID" />
                <asp:BoundField DataField="name" HeaderText="Name" />
            </Columns>
        </asp:GridView>
    </div>
.aspx.cs
    List<string> lcb = new List<string>();

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            ViewState["cbList"] = lcb;
        }

    

        string strTarget = "" + Request.Form["__EVENTTARGET"];
        string strCtl = strTarget.Split('$')[strTarget.Split('$').Length - 1];
        string strArg = "" + Request.Form["__EVENTARGUMENT"];

  
        HandleEvent(strCtl, strArg);
    }
    protected void gv_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            lcb = (List<string>)ViewState["cbList"];
            CheckBox cb = (CheckBox)e.Row.FindControl("cb");           
            cb.Attributes["OnClick"] = this.ClientScript.GetPostBackEventReference(cb, 

DataBinder.Eval(e.Row.DataItem,"id").ToString());

            if (lcb.Find(x => x.Equals( DataBinder.Eval(e.Row.DataItem, "id").ToString

().Trim())) != null)
                cb.Checked = true;
            else
                cb.Checked = false;
        }
    }

    private void HandleEvent(string strCtl, string strArg)
    {
        switch (strCtl)
        {
            case "cb":
                UpdateCBList(strArg);
                break;
        }
    }

    private void UpdateCBList(string strArg)
    {
        lcb = (List<string>)ViewState["cbList"];
        if (lcb.Find(x => x.Equals(strArg)) == null)
        {
            //no match,insert
            lcb.Add(strArg);
    
        }
        else
        {   
            //match,remove
            lcb.Remove(strArg);
        }

        //final
        ViewState["cbList"] = lcb;
       
    }
------ client side -------
說明:
在 gridview 的 RowDataBound 事件,將每一個 checkbox 都加上 key 的屬性,裡面存放唯一且可識別的資訊。再透過 jQuery 為每個 checkbox 註冊勾選時都要觸發函式:UpdateHiddenValue()。在這函式裡,會判斷被觸發的 checkbox 的 key ,是否已存在於隱藏欄位 hValue (注意,必須將這隱藏欄位加上 runat="server",即使分頁後,依然可以保存 hValue 裡面的值),如果不存在,則將 key 值加入 hValue ,否則則從 hValue 裡面移除。最後,讓 DataGridView 裡的 checkbox 都隨時與 hValue 保持勾選與否的資訊同步。

 .aspx
<head runat="server">
    <title></title>
    <script src="http://code.jquery.com/jquery-latest.js"></script>
    <script>
        $(function () {
            //定義 checkbox 被點選時所要觸發的動作
            $("input[id$=cb]").click(function () { UpdateHiddenValue(this.key); });

            //將隱藏欄位 hValue 的值與 DataGridView 的 checkbox 作對應更新
            $("input[id$=cb]").each(function (i) {
                if ((',' + $("input[id*=hValue]").val()).indexOf(',' + $(this).attr('key') 

+ ',') > -1) {
                    //已存在,將 checkbox 打勾
                    this.checked = true;
                }
                else {
                    //不存在,將 checkbox 勾選取消
                    this.checked = false;
                }
            });
        });


        function UpdateHiddenValue(strInput) {
            if ((',' + $("input[id*=hValue]").val()).indexOf(',' + strInput + ',') > -

1) {
                //已存在,將輸入的值從隱藏欄位 hValue 移除
                var v = ',' + $("input[id*=hValue]").val();
                v = v.replace(',' + strInput + ',', ',');
                v = v.substr(1, v.length - 1);
                $("input[id*=hValue]").val(v);
            }
            else {
                //未存在,將輸入的值加入隱藏欄位 hValue 
                $("input[id*=hValue]").val($("input[id*=hValue]").val() + strInput+',');
            }          

        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <asp:GridView ID="gv" runat="server" AutoGenerateColumns="False" 
            EnableModelValidation="True" AllowPaging="True" DataSourceID="dsData" 
            PageSize="5" onrowdatabound="gv_RowDataBound">
            <Columns>
                <asp:TemplateField>
                    <ItemTemplate>
                        <asp:CheckBox ID="cb" runat="server" />
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:BoundField DataField="id" HeaderText="ID" />
                <asp:BoundField DataField="name" HeaderText="Name" />
            </Columns>
        </asp:GridView>
        <input type="hidden" id="hValue" runat="server" />
    <asp:XmlDataSource ID="dsData" runat="server" DataFile="~/data.xml">
    </asp:XmlDataSource>
    <div>
    
    </div>
    </form>
</body>
.aspx.cs
    protected void gv_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            CheckBox cb = (CheckBox)e.Row.FindControl("cb");           
            cb.InputAttributes["key"] = string.Format("{0}", DataBinder.Eval

(e.Row.DataItem, "id"));
        }

    }
參考:
01:利用ASP.NET的HIDDENFIELD記錄GRIDVIEW的CHECKBOX狀態,並且加入全選、取消全選、分頁保留CHECKBOX狀態,達到類似GMAIL的郵件清單功能
02:GridView 欄位 CheckBox 全選及取消全選
03:測試程式下載

2 則留言:

  1. 謝謝~分享這麼有用的資訊
    但,我有一個問題,
    如何取得所有勾選的項目呢???
    我以gridview的row掃勾選的項目,
    僅能抓取到本頁中,感謝您的回答~

    回覆刪除
    回覆
    1. 在掃之前先設定取消分頁,掃完後,再設定為分頁模式

      刪除