by WebSurfer
2013年12月19日 16:59
GridView に CheckBox を配置し、ユーザーに複数行を選択してもらうというシナリオはよくあると思います。この場合、GridView にページング機能が実装されていると問題です。
例えば、Page 1 でいくつかの行を選択して CheckBox にチェックを入れてから Page 2 に移動し、再び Page 1 に戻った場合、先に Page 1 で CheckBox に入れたはずのチェックが消えてしまいます。
この問題に対処するためには、以前のチェック情報を ViewState に保持しておき、ページが変わったら ViewState からそのページのチェック情報を取得して CheckBox.Checked プロパティを true に設定してやるというような操作が必要になります。
ページャーがクリックされるとポストバックが発生しますので、そのタイミングでクライアントスクリプトによってチェック結果を取得して隠しフィールドに格納し、それをサーバーに送信して ViewState に保存するようにしてみました。
CheckBox の ID には、GridView.DataKeys プロパティから主キー値を取得し、それを設定しています。それゆえ、クライアントスクリプトでもチェックされた CheckBox の name 属性から当該レコードの主キー値が分かります。
ただし、ID から name への名付けルールが変わるとうまく行かなくなる可能性がありますので、できれば主キー値を GridView に表示して、それから取得するようにした方がいいかもしれません。
以下のソースコードは主キー値を name 属性から取得する場合の例です。上の画像がソースコードの実行結果です。
注意事項はソースコード内のコメントに書きましたので、詳しくはそれを見てください。手抜きですみません。(汗)
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
// CheckBox をクリックするたびポストバックしないで、ページン
// グでポストバックした際に、まとめてチェック結果を取得し、
// チェックされた行の主キー (ID) リストを更新する。
// クライアント側で、input type="checkbox" 要素の id 属性また
// は name 属性から、その要素のある行の主キー (ID) を取得する
// ため、CheckBox.ID に主キー値を設定。主キー値が 17 の場合
// id="GridView1_17_0" name="GridView1$ctl02$17" のようになる。
// このサンプルでは、クライアントスクリプトで name 属性から
// 主キー値を取得している。名前付けルールが変わってうまくいか
// なくなる可能性があるので注意。
// チェック入り行の主キー (ID) のリスト。ViewState に保持。
protected List<String> checkedIds;
protected void Page_Load(object sender, EventArgs e)
{
// ViewState から ID リスト checkedIds を取得。
if (checkedIds == null)
{
object obj = ViewState["CheckedIds"];
if (obj != null)
{
checkedIds = (List<String>)obj;
}
else
{
// ViewState が未設定の場合は新たに初期化。
checkedIds = new List<String>();
}
}
// form を送信する際、CheckBox のチェック有無を調べて、
// その結果をサーバーに送信するスクリプトを設定。
String csname = "OnSubmitScript";
Type cstype = this.GetType();
ClientScriptManager cs = Page.ClientScript;
if (!cs.IsOnSubmitStatementRegistered(cstype, csname))
{
String cstext =
"getCheckedIDs('" + GridView1.ClientID + "');";
cs.RegisterOnSubmitStatement(cstype, csname, cstext);
}
}
// ページャクリックの場合のイベント発生順序に注意:
// 現ページの GridView.RowCreated ⇒ Page.Load ⇒ GridView.
// PageIndexChanging ⇒ 次ページの GridView.RowCreated
// ちなみに、ボタンクリックによるイベント発生順序は:
// 現ページの GridView.RowCreated ⇒ Page.Load ⇒
// Button.Click
// CheckBox.ID を動的に設定するのを RowDataBound イベント
// で行うのは NG。ボタンクリックではデータバインドが起こ
// らないので、CheckBox.ID が設定されず(ASP.NET が勝手に
// 独自の ID を生成する)、うまくいかない。
protected void GridView1_RowCreated(object sender,
GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// ViewState から ID リスト checkedIds を取得。
// ボタンクリックまたはページングによるポストバック
// では Page.Load よりこちらのイベントが先に発生す
// るのでここでも必要。
if (checkedIds == null)
{
object obj = ViewState["CheckedIds"];
if (obj != null)
{
checkedIds = (List<String>)obj;
}
else
{
// ViewState が未設定の場合は新たに初期化。
checkedIds = new List<String>();
}
}
// この行の id(主キー)値を取得。
string id = ((GridView)sender).
DataKeys[e.Row.RowIndex].Value.ToString();
// e.Row.Cells[0] の中から CheckBox を探す。
foreach (Control control in e.Row.Cells[0].Controls)
{
if (control is CheckBox)
{
CheckBox cb = (CheckBox)control;
// CheckBox.ID に id を設定。クライアントスクリプト
// で CheckBox の name 属性から id を取得できるよう
// にするため。
// このサンプルでは name は GridView1$ctl02$17 のよ
// うになり、最後の 17 が id に該当。
cb.ID = id;
// id がリスト checkedIds にあればチェックを入れる。
foreach (string checkedid in checkedIds)
{
if (checkedid == id)
{
cb.Checked = true;
return;
}
}
cb.Checked = false;
break;
}
}
}
}
// ページが変更される際にリスト checkedIds を更新する。
protected void GridView1_PageIndexChanging(object sender,
GridViewPageEventArgs e)
{
// form の onsubmit イベントで、クライアントスクリプトが
// CheckBox のチェック有無の情報を取得し、当該行の id を
// 隠しフィールドに格納するので、それから id を取得する。
char[] chars = new char[] { ',' };
string s = Request.Form["__CHECKEDIDLIST"].Trim(chars);
string[] check = s.Split(chars);
s = Request.Form["__UNCHECKEDIDLIST"].Trim(chars);
string[] uncheck = s.Split(chars);
// 重複を避けるため、一旦、GridView 上のすべての ID を
// リスト checkedIds から削除してから、
foreach (string id in check)
{
checkedIds.Remove(id);
}
foreach (string id in uncheck)
{
checkedIds.Remove(id);
}
// チェック済の ID をリスト checkedIds に加える。
foreach (string id in check)
{
if (!String.IsNullOrEmpty(id))
{
checkedIds.Add(id);
}
}
// リスト checkedIds を ViewState に保持する。
ViewState["CheckedIds"] = checkedIds;
}
// ボタンクリックでチェックされた全行の id を書き出す。
protected void Button1_Click(object sender, EventArgs e)
{
// ボタンクリック直前のチェック結果をここで取得。
char[] chars = new char[] { ',' };
string s = Request.Form["__CHECKEDIDLIST"].Trim(chars);
string[] check = s.Split(chars);
s = Request.Form["__UNCHECKEDIDLIST"].Trim(chars);
string[] uncheck = s.Split(chars);
// チェック入り行の主キー (ID) リスト checkedIds を更新。
foreach (string id in check)
{
checkedIds.Remove(id);
}
foreach (string id in uncheck)
{
checkedIds.Remove(id);
}
foreach (string id in check)
{
if (!String.IsNullOrEmpty(id))
{
checkedIds.Add(id);
}
}
ViewState["CheckedIds"] = checkedIds;
// リスト checkedIds の内容を書き出す。
string str = "Selected Ids:";
foreach (string id in checkedIds)
{
str += " " + id;
}
Label1.Text = str;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
<script src="Scripts/jquery-1.8.3.js" type="text/javascript">
</script>
<script type="text/javascript">
//<![CDATA[
// CheckBox のチェック有無を調べて、その結果を隠し
// フィールドに設定するスクリプト。
function getCheckedIDs(gridViewClientId) {
var checked = "";
var unchecked = "";
$('#' + gridViewClientId + ' input:checkbox').each(
function () {
// このサンプルでは name は GridView1$ctl02$17
// のようになり、最後の 17 が id に該当。
// ただし MasterPage を使うなど、名前付コンテナ
// に GridView を入れると違ってくるので注意。
var name = $(this).attr('name');
var arrayOfString = name.split('$');
var id = arrayOfString[arrayOfString.length - 1];
if ($(this).attr('checked') == "checked") {
checked += ',' + id;
} else {
unchecked += ',' + id;
}
});
$('#__CHECKEDIDLIST').val(checked);
$('#__UNCHECKEDIDLIST').val(unchecked);
}
//]]>
</script>
</head>
<body>
<form id="form1" runat="server">
<%--チェック結果をサーバに送信する隠しフィールド。--%>
<input type="hidden"
name="__CHECKEDIDLIST"
id="__CHECKEDIDLIST"
value="" />
<input type="hidden"
name="__UNCHECKEDIDLIST"
id="__UNCHECKEDIDLIST"
value="" />
<div>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand= "SELECT [ProductID], [ProductName]
FROM [Products] ORDER BY [ProductID]">
</asp:SqlDataSource>
<asp:GridView ID="GridView1"
runat="server"
AllowPaging="True"
AutoGenerateColumns="False"
DataKeyNames="ProductID"
DataSourceID="SqlDataSource1"
OnPageIndexChanging="GridView1_PageIndexChanging"
OnRowCreated="GridView1_RowCreated">
<Columns>
<asp:TemplateField HeaderText="Select">
<ItemTemplate>
<asp:CheckBox runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="ProductID"
HeaderText="ProductID"
InsertVisible="False"
ReadOnly="True"
SortExpression="ProductID" />
<asp:BoundField DataField="ProductName"
HeaderText="ProductName"
SortExpression="ProductName" />
</Columns>
</asp:GridView>
<asp:Button ID="Button1"
runat="server"
Text="Show Checked Ids"
OnClick="Button1_Click" />
<asp:Label ID="Label1" runat="server"></asp:Label>
</div>
</form>
</body>
</html>