はじめに
先週の記事『GridView上のすべてのチェックボックスをオンにする方法』では、1個のボタンのクリックでGridView上のすべてのチェックボックスをオンまたはオフにする機能を追加する方法を説明しました。この機能を実現するにはサーバーサイドコードを使う方法と、クライアントサイドスクリプトを使う方法の2つがあり、両方について詳しく解説しました。どちらのバージョンでも、ページには2個のボタン――「すべて選択(Check All)」と「すべて選択解除(Uncheck All)」――が配置され、これをクリックするとすべてのチェックボックスがオンまたはオフになります。サーバーサイドコードを使うバージョンでは、どちらのボタンをクリックしてもポストバックが発生し、プログラムによってすべてのチェックボックスのオンまたはオフが実行されます。クライアントサイドのバージョンでは、チェックボックスのオン/オフは短いJavaScriptコードで処理されます。
今回の記事では、このユーザーインターフェイスを改良し、クライアントサイドスクリプトを利用して、GridViewのチェックボックス(CheckBox)列の上にあるヘッダー行に[すべて選択/選択解除(Check/Uncheck All)]チェックボックスを用意することにします(次のスクリーンショットを参照)。このヘッダーのチェックボックスをオンにするとGridView上のすべてのチェックボックスがオンになり、逆にこれをオフにするとGridView上のすべてのチェックボックスがオフになります。この機能を追加するのは最初に考えていたよりも少しトリッキーな作業で、ちょっとした工夫が必要でした。この記事では、そういった問題点と、それを解決するために使った迂回策について詳細に見ていきます。それでは説明に移ります。
前回の記事『GridView上のすべてのチェックボックスをオンにする方法』をまだ読んでいなければ、先に進む前に目を通しておいてください。
[Check/Uncheck All]チェックボックスをヘッダーに追加する
このユーザーインターフェイスを実装する最初のステップでは、チェックボックス列のヘッダーに[Check/Uncheck All]チェックボックスを追加します。GridView上のチェックボックス列はTemplateFieldとして実装したので、ヘッダーにCheckBoxを追加するのに必要な作業は、次のようにTemplateFieldにHeaderTemplateを追加することだけです(簡潔にするため、GridViewのフォーマット関連の設定は一部省略しました)。
<asp:GridView ID="FileList" runat="server" AutoGenerateColumns="False" DataKeyNames="FullName"> <Columns> <asp:TemplateField> <HeaderTemplate> <asp:CheckBox runat="server" ID="HeaderLevelCheckBox" /> </HeaderTemplate> <ItemTemplate> <asp:CheckBox runat="server" ID="RowLevelCheckBox" /> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="Name" HeaderText="Name" /> ... Remaining BoundFields ... </Columns> </asp:GridView>
このコードを追加すると、チェックボックス列のヘッダーにCheckBoxが配置されますが、このヘッダーのCheckBoxをクリックしても何も起こりません。ヘッダーのCheckBoxをオンにするとグリッド内のすべてのCheckBoxがオンになり、同様にヘッダーのCheckBoxをオフにするとグリッド内のすべてのCheckBoxがオフになるような仕組みが必要です。また、オン/オフの反応速度を改善するために、この処理を完全にクライアントサイドでJavaScriptを使って行うことも必要です。
「GridView上のすべてのチェックボックスをオンにする方法」では、JavaScript関数ChangeAllCheckBoxStates(checkState)
を詳しく見ていきました。この関数は、checkState
入力パラメータの値に基づいてグリッド上のすべてのCheckBoxをオンまたはオフにします。前回の記事で紹介したクライアントサイドのデモの場合は、この関数を[Check All]ボタンまたは[Uncheck All]ボタンから呼び出し、その際にcheckState
入力パラメータに値true
([Check All]の場合)またはfalse
([Uncheck All]の場合)を渡していました。今回まず考えたのは、このJavaScript関数をヘッダーのCheckBoxで再利用することでした。つまり、ヘッダーのCheckBoxのクライアントサイドonclickイベントでChangeAllCheckBoxStates
を呼び出し、checkState
パラメータにthis.checked
を渡すというものです。
この処理を行うために、Page_Load
イベントハンドラで以下のサーバーサイドコードを使用して、ヘッダーのCheckBoxのクライアントサイドonclickイベントを関連付けました。
Protected Sub Page_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load If Not Page.IsPostBack Then Dim dirInfo As New DirectoryInfo(Request.PhysicalApplicationPath) FileList.DataSource = dirInfo.GetFiles() FileList.DataBind() End If 'On every page visit we need to build up the CheckBoxIDs array 'Get the header CheckBox Dim cbHeader As CheckBox = _ CType(FileList.HeaderRow.FindControl( _ "HeaderLevelCheckBox"), CheckBox) 'Run the ChangeCheckBoxState client-side function whenever the 'header checkbox is checked/unchecked cbHeader.Attributes("onclick") = _ "ChangeAllCheckBoxStates(this.checked);" For Each gvr As GridViewRow In FileList.Rows 'Get a programmatic reference to the CheckBox control Dim cb As CheckBox = _ CType(gvr.FindControl("RowLevelCheckBox"), CheckBox) 'Add the CheckBox's ID to the client-side CheckBoxIDs array ClientScript.RegisterArrayDeclaration( _ "CheckBoxIDs", String.Concat("'", cb.ClientID, "'")) Next End Sub
Page_Load
イベントハンドラの太字ではない部分は、前回の記事からそのままコピーしたコードです。最初の部分では、ページが最初に表示されたときにデータをGridViewにバインドします(それ以降のポストバックではこのバインドを行いません)。For Each
ループの最後では、CheckBoxIDs
クライアントサイド配列を作成します。この配列は、ChangeAllCheckBoxStates
関数ですべてのグリッドのCheckBoxのオンまたはオフを行うときに、反復処理の対象となります。今回、[Check/Uncheck All]ヘッダーのCheckBoxをサポートするために追加したのが太字の部分です。特に、GridViewのHeaderRowのコントロールコレクションを検索してIDが"HeaderLevelCheckBox"のコントロールを見つけ、ヘッダーのCheckBoxへの参照を取得する部分に注目してください("HeaderLevelCheckBox"は、前述の宣言構文で宣言したヘッダーのCheckBoxのID値です)。
ヘッダーのCheckBoxへの参照を取得したら、次にクライアントサイドのonclickイベントハンドラでChangeAllCheckBoxStates
関数が呼び出されるようにします。そのためには、CheckBoxのAttributesコレクションのonclick設定にChangeAllCheckBoxStates
関数の呼び出しを割り当て、パラメータとしてthis.checked
を渡します。Attributesコレクションは、コントロールの外観となるHTMLに追加の属性値を指定するための手段です。クライアントサイドのHTML
属性の操作とサーバーサイドASP.NETコードの使用方法については、記事『Working with Client-Side Script』を参照してください。
このコードを追加したことにより、ヘッダーのCheckBoxがオンになるとChangeAllCheckBoxStates(true)
が呼び出されてグリッド上のすべてのCheckBoxがオンになり、逆にヘッダーのCheckBoxがオフになるとChangeAllCheckBoxStates(false)
が呼び出されてすべてのCheckBoxがオフになります。
問題1
以上の変更を行った後に最初に気がついた問題は、ページ上の[Check All]ボタンや[Uncheck All]ボタンをクリックしても、ヘッダーのCheckBoxがオンまたはオフにならないことでした。グリッド上の他のCheckBoxは、それまでどおり正しくオン、オフされます。原因は、この2つのボタンはChangeAllCheckBoxStates
関数を呼び出しており、ChangeAllCheckBoxStates
関数は、CheckBoxIDs配列だけを反復処理して、この配列に登録されたすべてのCheckBoxをオンまたはオフにしているからです。ヘッダーのCheckBoxはこの配列に含まれないため、ChangeAllCheckBoxStates
関数を呼び出してもオン/オフの状態は以前と変わりません(もちろん、ヘッダーのCheckBoxを直接オン/オフするときはこの問題は起こりません。ユーザーの操作によってCheckBoxのオン/オフが適切に処理されるからです)。
この問題を修正するため、単純な方法ですが、ヘッダーのCheckBoxをCheckBoxIDs配列内のIDリストに加えました。この追加処理は、Page_Load
イベントハンドラ内でヘッダーのCheckBoxのAttributes("onclick")
プロパティを設定した直後に行います。以下にそのコードを示します。
'Add the CheckBox's ID to the client-side CheckBoxIDs array ClientScript.RegisterArrayDeclaration( _ "CheckBoxIDs", String.Concat("'", cbHeader.ClientID, "'"))
ヘッダーのCheckBoxのIDを配列に追加すると、ユーザーが[Check All]ボタンと[Uncheck All]ボタンをクリックしたときに、ヘッダーのCheckBoxがグリッド上の下位のCheckBoxと共に正しくオンまたはオフになります。