GridViewからデータを追加する
GridViewにはデータを追加する機能がありません。データの追加も行いたいときにはDetailsViewやFormViewをInsertモードにしてGridViewと組み合わせて使うというのが一般的な方法になります。この方法であればコーディングを行うことなくデータの追加/修正/削除が可能になりますのでマスターのメンテナンス画面等はあっという間にできてしまいます。
それでも画面のレイアウト等を考えてGridViewの中からデータを追加したいという場合にはこの記事に述べるような作業を行うことで、データを追加できるGridViewを作成することができます。
今回以下のようなテーブルを用意しました。主キーのuserIDにはIDENTITYの指定を行っています。
aspxページにこのテーブルをドラッグ アンド ドロップすると、SqlDataSourceとGridViewが自動的に作成されます。
GridViewのスマートタグから編集と削除を有効にします。また、プロパティウィンドウでShowFooterプロパティをTrueに設定します。
GridViewのスマートタグで列の編集をクリックし、フィールドウィンドウを表示します。
「このフィールドをTemlateFieldに変換します」を利用して、選択されたフィールドのすべてをTemplateFieldに変換します。
GridViewのスマートタグでテンプレートの編集をクリックします。ドロップダウンリストから、まずはColumn[0](コマンド行)のFooterTemplateを選択します。テンプレートの中にLinkButtonをドラッグ アンド ドロップし、LinkButtonのCommandNameプロパティに"Insert"を、Textプロパティに"追加"を設定します。
次にColumn[2](userName)のFooterTemplateを選択します。テンプレートの中にTextBoxをドラッグ アンド ドロップし、コントロールのIDを"InsertUserName"に変更します。
同じように、Column[3](mailAddress)のFooterTemplateにもTextBoxをドラッグ アンド ドロップし、コントロールのIDを"InsertMailAddress"に変更します。
テンプレートの編集を終了すると、GridViewは以下のようになります。
次に追加ボタンがクリックされたときのコードを記述します。GridViewのイベントウィンドウでRowCommandをダブルクリックします。コードウィンドウにGridView1_RowCommandメソッドが作成されるので、ここに以下のとおりに記述します。
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Insert")
{
SqlDataSource1.InsertParameters.Clear();
foreach (string key in Request.Form.AllKeys)
{
if (key.Contains("$InsertUserName"))
SqlDataSource1.InsertParameters.Add(new ControlParameter("userName", TypeCode.String, key, "Text"));
if (key.Contains("$InsertMailAddress"))
SqlDataSource1.InsertParameters.Add(new ControlParameter("mailAddress", TypeCode.String, key, "Text"));
}
SqlDataSource1.Insert();
}
}
ここで行っている処理の本質は、追加ボタンがクリックされたとき、つまり追加ボタンに設定したInsertというCommandNameを受け取ったときにSqlDataSource1のInsertメソッドを実行するという部分です。
ただし、このときInsertメソッドを実行するには追加するデータをSqlDataSource1に渡さなければなりません。追加するデータはInsertUserName、InsertMailAddressというTextBoxのTextプロパティの値です。このようにコントロールの値をSqlDataSourceに渡すにはControlParameterを利用するのがもっとも適しています。
もし、InsertUserNameやInsertMailAddressがaspxページに直接貼り付けられたコントロールだったら、そのときはaspxページの中でSqlDataSource1のInsertParametersにそれらのTextBoxを読み取るControlParameterを設定するだけで十分です。上記のコードのforeach文は必要ありません。しかしここではInsertUserNameやInsertMailAddressはGridViewのFooterTemplateの中に貼り付けられています。この場合、これらのTextBoxは動的に生成されます。その結果、GridViewに表示されるデータの数によってコントロールを特定するための名前が変わってしまうのです。
コントロールを特定するための名前はブラウザ上でそのコントロールにつけられるNameプロパティの値が使えます。上記のプログラムではブラウザからのRequestを解析することでGridViewの中に生成されたコントロール名を取り出し、そのコントロール名をもとにControlParameterを作成してSqlDataSource1に設定する、という処理を行っています。
さてこれで実装は終了、、、というわけではありません。
ここまでの状態でaspxページを実際に動作させてみると、画面は以下のようになってしまいます。
GridViewはデータがまったく存在しない場合、EmptyDataTextプロパティに設定されている文字列を表示します。これを変更するにはEmptyDataTemplateを編集する必要があります。
GridViewのスマートタグでテンプレートの編集をクリックし、EmptyDataTemplateを選択します。ここにTableを追加し、その中にLinkButtonと2つのTextBoxをドラッグ アンド ドロップします。
ここで注意することは、LinkButtonのCommandNameプロパティを"Insert"にすること、TextBoxのIDをそれぞれ"InsertUserName"、"InsertMailAddress"に変更すること、です。
TextBoxのIDを前とまったく同じに設定することにより、同じIDの2つのTextBoxが1つのaspxファイルの中に存在してしまうように思えるかもしれません。しかし、これらのTextBoxは動的に生成されるものであり、同時に存在することはありませんので安心してください。
さて、テンプレートの編集を終了し、あらためてaspxページを実行します。
EmptyDataTemplateの見栄えは調整する必要がありますが、プログラムの動作には問題ありません。データを追加してみましょう。
正しく動作することが確認できると思います。
◆◆◆ 追記 ◆◆◆
GridView1_RowCommandメソッドですが、以下のような記述をすることもできます。
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Insert")
{
SqlDataSource1.InsertParameters.Clear();
string nameControl = null;
string mailControl = null;
if (GridView1.Rows.Count == 0)
{
nameControl = GridView1.Controls[0].Controls[0].FindControl("InsertUserName").UniqueID;
mailControl = GridView1.Controls[0].Controls[0].FindControl("InsertMailAddress").UniqueID;
}
else
{
nameControl = GridView1.FooterRow.FindControl("InsertUserName").UniqueID;
mailControl = GridView1.FooterRow.FindControl("InsertMailAddress").UniqueID;
}
SqlDataSource1.InsertParameters.Add(new ControlParameter("userName", TypeCode.String, nameControl, "Text"));
SqlDataSource1.InsertParameters.Add(new ControlParameter("mailAddress", TypeCode.String, mailControl, "Text"));
SqlDataSource1.Insert();
}
}
データがまったく存在せず、EmptyDataTemplateの内容が表示されているとき(GridView1.Rows.Count == 0)のコードがいまいちなんですが、こちらのほうがコードが何をしようとしているかはよみとりやすいと思います。
◆◆◆ 追記 その2 ◆◆◆
とあるBlogでコントロールを再帰によって取り出す方法が提示されていました。
この方法を使うとコードはだいぶすっきりします。
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Insert")
{
SqlDataSource1.InsertParameters.Clear();
SqlDataSource1.InsertParameters.Add(new ControlParameter("userName", TypeCode.String, FindControlRecursive(GridView1, "InsertUserName").UniqueID, "Text"));
SqlDataSource1.InsertParameters.Add(new ControlParameter("mailAddress", TypeCode.String, FindControlRecursive(GridView1, "InsertMailAddress").UniqueID, "Text"));
SqlDataSource1.Insert();
}
}
protected Control FindControlRecursive(Control Root, string Id)
{
if (Root.ID == Id)
return Root;
foreach (Control Ctl in Root.Controls)
{
Control FoundCtl = FindControlRecursive(Ctl, Id);
if (FoundCtl != null)
return FoundCtl;
}
return null;
}
コントロール名をあわせておけばデータの存在/非存在にかかわらず同じコードで対応できるところがよいですね。
自分としてはこの方法を決定稿としようと思います。