.NET TIPS

[ASP.NET]GridViewコントロールにコマンド・ボタンを配置するには?(応用編)[2.0、3.0、3.5、C#、VB]

山田 祥寛
2009/01/15

 「TIPS:[ASP.NET]GridViewコントロールにコマンド・ボタンを配置するには?(基本編)」では、ButtonField/CommandFieldフィールドを利用して、GridViewコントロールにコマンド・ボタンを配置する方法を紹介した。

 そのTIPSでは、ただ単にボタンをクリックした行を特定して、行の背景色を一様に変更するだけであったが、実際のアプリケーションでは行の主キー値を取得して、その値に応じて何らかの処理を行いたい、というケースが多いはずだ。そこで本稿では、コマンド・ボタンをクリックした際に、行の主キー値を取得するためのいくつかのアプローチを、具体的なサンプルとともに紹介する。

 なお、本稿で作成するサンプルは、「TIPS:[ASP.NET]GridViewコントロールでデータソースの内容を表示するには?」で作成したサンプルを基に、その差分のみを解説している。GridViewコントロールそのものの利用方法については、このTIPSも併せてご参照いただきたい。

●DataKeysプロパティを利用する

 ここで作成するのは、[詳細]ボタンをクリックすると、対応する行の詳細ページに移動するというサンプルである。移動先は、

http://www.wings.msn.to/index.php/-/A-03/<ISBNコード>/

のような形式で、主キー(ISBNコード)の値より決まるものと仮定する。

 もちろん、これと同様の機能は、「TIPS:GridViewコントロールでハイパーリンクを表示するには?」で紹介したHyperLinkFieldフィールドを利用しても実現できるが、ButtonFieldフィールドを利用することで、リダイレクト時にアクセス・ログを記録するなど、任意の処理を自由に追加できるというメリットがある。

 以下はその[詳細]ボタンを利用したサンプルの実行例だ。

先頭行の[詳細]ボタンをクリック
作成するサンプルの実行結果
[詳細]ボタンをクリックすると、対応する書籍の詳細ページに移動する。

1. ButtonFieldフィールドを追加する

 ButtonFieldフィールドは、[フィールド]ダイアログから追加できる。ここではButtonFieldフィールドがGridViewコントロールの先頭列となるように追加したうえで、以下の表の要領でプロパティ情報を設定しておく。

プロパティ 設定値
ButtonType Button
CommandName Details
Text 詳細
追加したButtonFieldフィールドのプロパティ設定

2. コマンド・ボタンの挙動を定義する

 次に、RowCommandイベント・ハンドラを定義して、[詳細]ボタンがクリックされたときの挙動を定義する。

 RowCommandイベント・ハンドラを追加するには、コード・エディタを開き、画面上部の選択ボックスから[grid](GridViewコントロールのID値)−[RowCommand]を選択すればよい。もしくは、プロパティ・ウィンドウをイベント表示に切り替え、RowCommandの欄をダブルクリックしてもよい。

イベント・ハンドラの骨組みを自動生成

 イベント・ハンドラの骨組みができるので、ここでは以下のようなコードを追加する。

protected void grid_RowCommand(Object sender, GridViewCommandEventArgs e) {

  // コマンド名が“Details”の場合にのみ処理
  if (e.CommandName == "Details") {
    int args = Int32.Parse((String)e.CommandArgument);

    // 主キー(isbn列)の値を取得
    String isbn = (String)grid.DataKeys[args].Value;

    // isbn列の値を基にURLを生成し、リダイレクト
    Response.Redirect(String.Format(
      "http://www.wings.msn.to/index.php/-/A-03/{0}/", isbn));
  }
}
Protected Sub grid_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs)

  ' コマンド名が“Details”の場合にのみ処理
  If e.CommandName = "Details" Then

    ' 主キー(isbn列)の値を取得
    Dim isbn As String = grid.DataKeys(e.CommandArgument).Value

    ' isbn列の値を基にURLを生成し、リダイレクト
    Response.Redirect(String.Format( _
      "http://www.wings.msn.to/index.php/-/A-03/{0}/", isbn))
  End If
End Sub
コマンド・ボタンをクリックしたタイミングで実行されるコード(上:C#、下:VB)

 グリッド表に含まれる主キーのリストは、GridViewコントロールのDataKeysプロパティを介して取得できる。DataKeysプロパティは、主キー列の値リストをDataKeyArrayオブジェクト(System.Web.UI.WebControls名前空間)として返す。

 ここでは、GridViewCommandEventArgsオブジェクトのCommandArgumentプロパティによって取得した行番号をキーに、DataKeyArrayオブジェクトから該当する行のDataKeyオブジェクトを取り出している(CommandArgumentプロパティについては、「TIPS:同(基本編)」を参照していただきたい)。

 また、主キー値そのものを取得するには、取得したDataKeyオブジェクトのValueプロパティにアクセスすればよい。

 主キー値が取得できてしまえば、あとは簡単、これを基にリンク先のURLを整形し、Response.Redirectメソッドでリダイレクト処理を行うだけである。

 以上を理解できたら、さっそくサンプルを実行してみよう。[詳細]ボタンをクリックすることで、それぞれの行に対応する詳細ページが表示されれば成功である。

 なお、本サンプルを動作するうえで注意しておきたい点を、以下にまとめておく。

(1)DataKeyNamesプロパティが設定されているか

 DataKeyNamesプロパティは、GridViewコントロールにバインドされたデータソースの主キー列を表す。GridViewコントロールは、この設定に基づいて、DataKeysプロパティに主キー値をセットする。主キー値を取得しようとすると、例外が発生してしまったという場合には、DataKeyNamesプロパティが正しく設定されているかどうかを確認してほしい*1

 データバインドをデータソース構成ウィザードから行った場合は、通常、DataKeyNamesプロパティの設定を意識する必要はないはずだ。

*1 主キー値を取得する場合だけでなく、更新や削除を行う場合にもDataKeyNamesプロパティの情報は利用されている。更新/削除が正しく行えない場合にもDataKeyNamesプロパティを確認してみるとよい。

(2)CommandNameプロパティによる判定は必須

 GridViewコントロールにButtonFieldフィールドが1つしかない場合、上記リストにあるようなCommandNameプロパティによる条件分岐は不要に思われるかもしれない(ここではコマンド名が“Details”の場合にのみ、リダイレクト処理を行っている)。

 しかし、結論からいってしまうと、この条件分岐は不可欠である。試しに「If……End If」による条件分岐を削除したうえで、サンプルを実行し、[編集]ボタンをクリックしてみてほしい。すると、[詳細]ボタンをクリックした場合と同様、ページが移動してしまう。

 このことからいえるのは、RowCommandイベントはButtonFieldフィールドをクリックした場合にのみ発生するわけではないということだ。CommandFieldフィールド上のボタンをクリックした場合にも、RowCommandイベントは発生する。CommandFieldフィールドとButtonFieldフィールドとはまったく別物というわけではなく、ButtonFieldフィールドの特殊形がCommandFieldフィールドと理解しておくとよいだろう。

 ちなみに、グリッド表にCommandField/ButtonFieldフィールドを両方含めて1つしかボタンがない場合には、コマンド名による条件分岐は不要なのだろうか。しかし、これまた不可欠である。試しに、先ほど同様、「If……End If」を削除した状態で、今度はソート(タイトル行をクリック)、またはページング処理を行ってみよう。RowCommandイベントで例外が発生してしまうはずである。ソート/ページングのためのリンク・ボタンもまた、RowCommandイベントを発生するのである。

 まとめると、ソート/ページング機能が無効で、かつ、ボタン列が1つしかない状態であれば、コマンド名による分岐をしなくても、一応、RowCommandイベントは正しく動作する。しかし、そのようなケースでも後々に機能を増やした場合のことを考えれば、

RowCommandイベント・ハンドラでは、コマンド名による分岐は必須

と覚えておくのが無難だろう。

[注意]

上記の理由から、更新や削除、選択時の処理をRowCommandイベント・ハンドラで記述することも可能だ。ただし、これらの処理については、RowUpdating/RowUpdatedのような専用のイベントが別に用意されているため、通常は、こちらのイベントを利用するのが好ましい。具体的な方法については、後日、「TIPS:[ASP.NET]GridViewコントロールで更新/削除処理後の処理をカスタマイズするには?」で解説する予定だ。

●Cellsプロパティを利用する

 主キー値を取得するには、Cellsプロパティを利用することも可能だ(※当然ではあるが、この方法が使えるのは、主キー値がグリッド表に出力されている場合に限られる)。先ほどのRowCommandイベント・ハンドラを次のように書き換えてみよう(書き換え部分は太字)。

protected void grid_RowCommand(Object sender, GridViewCommandEventArgs e) {
  if (e.CommandName == "Details") {
    int args = Int32.Parse((String)e.CommandArgument);

    // クリックされた行の3番目の列のテキストを取得
    String isbn = grid.Rows[args].Cells[2].Text;

    Response.Redirect(String.Format(
      "http://www.wings.msn.to/index.php/-/A-03/{0}/", isbn));
  }
}
Protected Sub grid_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs)
  If e.CommandName = "Details" Then

    // クリックされた行の3番目の列のテキストを取得
    Dim isbn As String = grid.Rows(e.CommandArgument).Cells(2).Text

    Response.Redirect(String.Format( _
      "http://www.wings.msn.to/index.php/-/A-03/{0}/", isbn))
  End If
End Sub
コマンド・ボタンをクリックしたタイミングで実行されるコード(上:C#、下:VB)

 Rowsプロパティによって行(GridViewRowオブジェクト)を取得する方法については、「TIPS:同(基本編)」で紹介したとおりだ。

 GridViewRowオブジェクトからさらに個々のセル(DataControlFieldCellオブジェクト)にアクセスするには、Cellsプロパティに取得したい列番号を与えればよい。あとは、このTextプロパティにアクセスすることで、セル内のテキストにアクセスできる*2

*2 ただし、セル内の構造によっては、DataControlFieldCellオブジェクトのTextプロパティにアクセスするだけでは、目的の値にアクセスできない場合もあり得る。この点に関する詳細は、後日「TIPS:[ASP.NET]GridViewコントロールの各セルにアクセスするには?」で解説する予定だ。

 Cellsプロパティによるアクセスは、先ほどのDataKeysプロパティによるそれに比べると、グリッド表の構造を意識する必要があり、また、列の配置にコードが左右されやすいため、不適切にも思われるかもしれない。しかし、反面、この方法であれば、主キー値に限らず、行内のすべての値にアクセスすることが可能だ。

●RowDataBoundイベントを利用する

 コマンド・ボタンで利用する引数(例えば、上の例であれば主キー値)については、ButtonFieldフィールドのCommandArgumentプロパティにあらかじめセットしておくことも可能だ。

 ただし、ここで注意しなければならないのは、(なぜか)ButtonFieldフィールドそのものはCommandArgumentプロパティを公開していないという点である*3

*3 個人的には、CommandArgumentFieldのようなプロパティがあって、CommandArgumentの値をデータソースからバインドできてもよいと思うのだが。

 CommandArgumentプロパティの値をセットするには、RowDataBoundイベント・ハンドラを利用して、ButtondFieldフィールド配下のボタン・コントロール*4を取り出し、そのCommandArgumentプロパティを直接に操作する必要がある。RowDataBoundイベントは、行にデータがバインドされたタイミングで発生するイベントである。

*4 ButtonTypeプロパティの種類によっては、Button、LinkButton、ImageButtonコントロールのいずれかとなる。
 

 それでは、先ほどのサンプルを、RowDataBoundイベントを利用して書き換えてみよう。具体的なコードは、次のとおりである。

protected void grid_RowDataBound(Object sender, GridViewRowEventArgs e) {

  // 現在の行がデータ行である場合に、以降の処理を実行
  if (e.Row.RowType == DataControlRowType.DataRow) {

    // 1番目のセル配下に含まれるButtonコントロールを取得
    Button detail  = (Button)e.Row.Cells[0].Controls[0];

    // ButtonコントロールのCommandArgumentプロパティに
    // 主キー値をセット
    detail.CommandArgument =
      (String)grid.DataKeys[e.Row.RowIndex].Value;
  }
}

protected void grid_RowCommand(Object sender, GridViewCommandEventArgs e) {

  // コマンド名が「Details」の場合、指定されたURLにリダイレクト
  if (e.CommandName == "Details") {
    Response.Redirect(String.Format(
      "http://www.wings.msn.to/index.php/-/A-03/{0}/",
       e.CommandArgument));
  }
}
Protected Sub grid_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs)

  ' 現在の行がデータ行である場合に、以降の処理を実行
  If e.Row.RowType = DataControlRowType.DataRow Then

    ' 1番目のセル配下に含まれるButtonコントロールを取得
    Dim detail As Button = DirectCast(e.Row.Cells(0).Controls(0), Button)

    ' ButtonコントロールのCommandArgumentプロパティに
    ' 主キー値をセット
    detail.CommandArgument = _
      grid.DataKeys(e.Row.RowIndex).Value
  End If
End Sub

Protected Sub grid_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs)

  ' コマンド名が「Details」の場合、指定されたURLにリダイレクト
  If e.CommandName = "Details" Then
    Response.Redirect(String.Format( _
      "http://www.wings.msn.to/index.php/-/A-03/{0}/", _
       e.CommandArgument))
  End If
End Sub
コマンド・ボタンをクリックしたタイミングで実行されるコード(上:C#、下:VB)

 RowDataBoundイベント・ハンドラの第2引数であるGridViewRowEventArgsオブジェクト(System.Web.UI.WebControls名前空間)からは、Rowプロパティにアクセスすることで、バインド先の行(GridViewRowオブジェクト)を取得できる。

 では、そのRowTypeプロパティから現在行の種類を判定している。RowTypeプロパティの取り得る値は次のとおり。

メンバ名 概要
DataRow データ行(データバインドが可能な行)
EmptyDataRow 空行
Footer フッタ行
Header ヘッダ行
Pager ページャ行
RowTypeプロパティの戻り値(System.Web.UI.WebControls.DataControlRowType列挙体のメンバ)

 ここでは、現在行がデータ行である場合にのみ後続の処理を行っているわけだ。RowDataBoundイベントは、ヘッダ/フッタ行やページャ行など、すべての行について発生するため、処理を記述するに当たっては必ず行の種類で分岐を行う必要がある。

 次に、主キー列(isbn列)を表すセルを取得する。前述したように、GridViewRowオブジェクトから個別のセル(DataControlFieldCellオブジェクト)にアクセスするには、Cellsプロパティにアクセスすればよい。ここでは、さらにそのControlsプロパティを介して、配下に配置されている0番目の子コントロールを取得しているわけだ。これでButtonFieldフィールドのButtonコントロールが取得できた。

 あとは、Button.CommandArgumentプロパティに、DataKeysプロパティを介して取得した主キー値をセットするだけだ。DataKeysプロパティに渡す行番号は、GridViewRow.RowIndexプロパティで取得できる()。

 RowCommandイベント・ハンドラを見てみると、今度はGridViewCommandEventArgs.CommandArgumentプロパティで、主キー値に直接アクセスできていることが確認できるはずだ(太字部分)。

 ちなみに、ここでは標準的なButtonFieldフィールドを利用することにこだわったが、GridViewコントロールにはテンプレートを用いて独自のコントロールを配置するためのTemplateFieldフィールドが用意されている。これについては、後日、「TIPS:[ASP.NET]GridViewコントロールの削除ボタンで確認メッセージを表示するには?」でも解説する予定だ。End of Article

利用可能バージョン:.NET Framework 2.0
利用可能バージョン:.NET Framework 3.0
利用可能バージョン:.NET Framework 3.5
カテゴリ:Webフォーム 処理対象:GridViewコントロール
使用ライブラリ:GridViewコントロール
使用ライブラリ:GridViewRowEventArgsクラス(System.Web.UI.WebControls名前空間)
使用ライブラリ:DataKeyArrayクラス(System.Web.UI.WebControls名前空間)
関連TIPS:[ASP.NET]GridViewコントロールにコマンド・ボタンを配置するには?(基本編)
関連TIPS:[ASP.NET]GridViewコントロールでデータソースの内容を表示するには?
関連TIPS:[ASP.NET]GridViewコントロールで更新/削除処理後の処理をカスタマイズするには?(後日公開予定)
関連TIPS:[ASP.NET]GridViewコントロールの各セルにアクセスするには?(後日公開予定)
関連TIPS:[ASP.NET]GridViewコントロールの削除ボタンで確認メッセージを表示するには?(後日公開予定)

この記事と関連性の高い別の.NET TIPS
[ASP.NET]GridViewコントロールにコマンド・ボタンを配置するには?(基本編)
[ASP.NET]GridViewコントロールに選択ボタンを追加するには?
[ASP.NET]GridViewコントロールの削除ボタンで確認メッセージを表示するには?
[ASP.NET]GridViewコントロールで各列の表示をカスタマイズするには?
[ASP.NET]GridViewコントロールのヘッダにソート方向を表示するには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間