WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

GridView 内の全 CheckBox をオン

by WebSurfer 2010年10月7日 23:55

先の 記事で CheckBox にチェックを入れる/外すことによって、当該 CheckBox のテキストのスタイル(文字の色、下線の有無)を変化させる方法を書きました。

今回はその続編(?)として、GirdView の各行に配置した複数の CheckBox に、一度に全部チェックを入れたり外したりする操作を、クライアントサイドのスクリプトで実行する方法を書きます。

GridView 内の全 CheckBox をオン

書きますといっても、コードは 4 Guys From Rolla.com の ページ "Checking All CheckBoxes in a GridView Using Client-Side Script and a Check All" からほぼ丸写しさせてもらったものですが。

[Check All]ボタンをクリックすると、ヘッダーを含むすべての行の CheckBox にチェックが入るようになっています。[Uncheck All]ボタンをクリックすると、すべての行の CheckBox のチェックが外れるようになっています。

ヘッダーの CheckBox にチェックを入れると、各行に配置した CheckBox 全部にチェックが入ります。その状態から、ヘッダーの CheckBox のチェックを外すと、各行の CheckBox のチェックも外れるようになっています。

また、各行に配置した CheckBox に手動で一つ一つチェックを入れていって、すべての CheckBox にチェックが入った時、ヘッダーの CheckBox にもチェックが入るようになっています。その状態から、各行に配置した CheckBox の一つでもチェックを外すと、ヘッダーの CheckBox のチェックも外れるようになっています。

まずは普通の JavaScript を使って書きました。コードは以下の通りです。jQuery を使ってみましたが、かなりコード量を減らすことができるのは確かのようです。別途、jQuery バージョンもアップしますので、乞うご期待。(笑)

コードビハインド

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

public partial class _051_CheckUncheckAllCheckBoxesInGridView_1_ : System.Web.UI.Page
{
  DataTable CreateDataTable()
  {
    DataTable dt = new DataTable();
    DataRow dr;

    dt.Columns.Add(new DataColumn("Item", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Name", typeof(string)));
    dt.Columns.Add(new DataColumn("Price", typeof(Int32)));

    for (int i = 0; i < 10; i++)
    {
      dr = dt.NewRow();
      dr["Item"] = i;
      dr["Name"] = "Item " + i.ToString();
      dr["Price"] = 123000 * (i + 1);
      dt.Rows.Add(dr);
    }
    return dt;
  }

  void Page_Load(Object sender, EventArgs e)
  {
    if (!IsPostBack)
    {
      GridView1.DataSource = CreateDataTable();
      GridView1.DataBind();
    }
  }

  protected void GridView1_DataBound(object sender, EventArgs e)
  {
    CheckBox cbHeader =
      (CheckBox)GridView1.HeaderRow.FindControl("CheckBox1");
   cbHeader.Attributes["onclick"] =
      "ChangeAllCheckBoxStates(this.checked);";

    List<string> ArrayValues = new List<string>();
    ArrayValues.Add(String.Concat("'", cbHeader.ClientID, "'"));

    foreach (GridViewRow row in GridView1.Rows)
    {
      CheckBox cb = (CheckBox)row.FindControl("CheckBox2");
      cb.Attributes["onclick"] = "ChangeHeaderAsNeeded();";
      ArrayValues.Add(String.Concat("'", cb.ClientID, "'"));
    }

    CheckBoxIDsArray.Text =
      "<script type=\"text/javascript\">\n" +
      "//<![CDATA[\n" +
      String.Concat("var CheckBoxIDs =  new Array(",
                     String.Join(",", ArrayValues.ToArray()),
                     ");\n"
                   ) +
      "//]]>\n" +
      "</script>";
  }
}

aspx ファイル

<%@ Page Language="C#" AutoEventWireup="true" 
  CodeFile="051_CheckUncheckAllCheckBoxesInGridView(1).aspx.cs" 
  Inherits="_051_CheckUncheckAllCheckBoxesInGridView_1_" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Check/Uncheck All CheckBoxes in a GridView</title>
  <script type="text/javascript">
    <!--
    function ChangeCheckBoxState(id, checkState) {
      var cb = document.getElementById(id);
      if (cb != null) {
        cb.checked = checkState;
      }
    }

    function ChangeAllCheckBoxStates(checkState) {
      if (CheckBoxIDs != null) {
        for (var i = 0; i < CheckBoxIDs.length; i++) {
          ChangeCheckBoxState(CheckBoxIDs[i], checkState);
        }
      }
    }

    function ChangeHeaderAsNeeded() {
      if (CheckBoxIDs != null) {
        for (var i = 1; i < CheckBoxIDs.length; i++) {
          var cb = document.getElementById(CheckBoxIDs[i]);
          if (!cb.checked) {
            ChangeCheckBoxState(CheckBoxIDs[0], false);
            return;
          }
        }
        ChangeCheckBoxState(CheckBoxIDs[0], true);
      }
    }
    //-->>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h2>Check/Uncheck All CheckBoxes</h2>
    <input type="button" 
      value="Check All" 
      onclick="ChangeAllCheckBoxStates(true);" /> 
    <input type="button" 
      value="Uncheck All" 
      onclick="ChangeAllCheckBoxStates(false);" />
        
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      OnDataBound="GridView1_DataBound">
      <Columns>
        <asp:TemplateField>
          <HeaderTemplate>
            <asp:CheckBox runat="server" ID="CheckBox1" />
          </HeaderTemplate>
          <ItemTemplate>
            <asp:CheckBox runat="server" ID="CheckBox2" />
          </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField HeaderText="Item" DataField="Item" />
        <asp:BoundField HeaderText="Name" DataField="Name" />
        <asp:BoundField HeaderText="Price" DataField="Price" />
      </Columns>
    </asp:GridView>
    <asp:Literal ID="CheckBoxIDsArray" runat="server"></asp:Literal>    
  </div>
  </form>
</body>
</html>

--------------- 2010/10/8 追記 ---------------

上記は、普通の JavaScript を使ったものですが、jQuery を使って書き換えたものを別の記事 GridView 内の全 CheckBox をオン(jQuery) にアップしました。jQuery のセレクタのパワーを垣間見たような気がします。ぜひ違いを見てください。

Tags: ,

JavaScript

GridView に複数のテーブルを表示

by WebSurfer 2010年9月10日 13:10

あるテーブルを表示している GridView に 1 列追加し、そこに別テーブルからその行に関連する複数のレコード/フィールドを取得して表示するという話です。

結果は以下のような画面になります。MaterialDetails が追加した列です。

実行結果の画面

以下に具体的な例を説明します。

データベースで、「製品」は Products テーブルにて、「原料」は Materials テーブルにて管理されているとします。「製品」に使用されている「原料」の情報は Products や Materials テーブルには埋め込まず、関連を Relations という別テーブルで管理するとします。以下のような感じです。

製品テーブル (Products)
ProductID     int
ProductName   nvarchar(50)
UnitPrice     money

関連テーブル (Relations)
ProductID     int
MaterialID    int

原料テーブル (Materials)
MaterialID    int
MaterialName  nvarchar(50)
SupplierID    int

Products テーブルをベースに作った「製品」一覧の GridView に 1 列追加して、その列に、「製品」に使われている「原料」を表示します。

Materials テーブルから、関連するレコードの MaterialID, MaterialName, SupplierID をRelations テーブルを基に抽出し、追加した列に表示します。

製品 X には原料 a, b が、製品 Y には原料 a, e, f, h が使われているというように、追加した列に表示する Materials テーブルのレコード数は各行で一定ではありません。それに対応するための工夫が必要です。

具体的な手順は、以下のようになります。「原料」で表示するのが 1 フィールドだけでよいということであれば、もう少し簡単にできますが、基本的には以下の手順と同様です。

(1) Products の 型付 DataSet + TableAdapter

ソリューションにデータセット(xsd ファイル)を追加します。それにツールボックスから TableAdapter をドラッグ&ドロップします。

自動的に「TableAdapter 構成ウィザード」が起動しますので、そのウィザードを利用して Products テーブル用の型付 DataSet + TableAdapter を作成します。

基になる SELECT クエリは下記のようにします。

SELECT ProductID, ProductName, UnitPrice
FROM   Products

ウィザードが完了したら xsd ファイルを保存してください。その後、デザイナ画面のテーブル部分を右クリックして、[追加]→[列]で MaterialDetails 列を追加します。型はデフォルトで String になるはずです。

(2) Materials の型付 DataSet + TableAdapter

上記と同じ xsd ファイルの画面にツールボックスからもう一つ TableAdapter をドラッグ&ドロップし、クエリビルダで Materials テーブルと Relations テーブルの二つを追加します。

基になる SELECT クエリは下記のようにし、ウィザードを完了させます。

SELECT m.MaterialID, m.MaterialName, m.SupplierID
FROM   Materials AS m
       INNER JOIN Relations AS r
       ON m.MaterialID = r.MaterialID
WHERE  (r.ProductID = @ProductID)

上記 (1), (2) の結果は、以下の画面のようになります。

型付 DataSet + TableAdapter のデザイン画面

(3) GetProductsWithMaterials メソッド

ソリューションにクラスファイルを追加します。Products, Materials テーブルから必要なデータを抽出し、上記 (1) で作った型付 DataTable を初期化して返すメソッドを作成し、クラスファイルに実装します。

Products テーブルの TableAdapter を拡張する形で partial class にしてください。以下のような感じです。

using System;
using System.IO;
using System.ComponentModel;

namespace ProductsDataSetTableAdapters
{
  public partial class ProductsTableAdapter
  {
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public ProductsDataSet.ProductsDataTable GetProductsWithMaterials()
    {
      ProductsDataSet.ProductsDataTable prodTable = this.GetData();
      MaterialsTableAdapter adapter = new MaterialsTableAdapter();
      foreach (ProductsDataSet.ProductsRow row in prodTable.Rows)
      {
        ProductsDataSet.MaterialsDataTable mtrlTable = 
          adapter.GetData(row.ProductID);
        StringWriter writer = new StringWriter();
        mtrlTable.WriteXml(writer);
        row.MaterialDetails = writer.ToString();
      }
      return prodTable;
    }
  }
}

(4) aspx ファイル

データを表示する Web ページ(aspx ファイル)を作成します。ObjectDataSource と GridView を利用した例は下記の通りです。MaterialDetails 列の xml データは GridView の RowDataBound イベントで適宜書き換えます。

この例では、xml データを DataTable に戻して、GridView の中にもう一つ GridView を作って表示しています。GridView を使わず、文字列で表示するなど、別の方法も検討してみてください。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      string xmlMaterialDetails = 
        (string)((DataRowView)e.Row.DataItem)["MaterialDetails"];
      StringReader reader = new StringReader(xmlMaterialDetails);
      ProductsDataSet.MaterialsDataTable table = 
        new ProductsDataSet.MaterialsDataTable();
      table.ReadXml(reader);
      GridView gv = new GridView();
      gv.DataSource = table;
      e.Row.Cells[3].Controls.Clear();
      e.Row.Cells[3].Controls.Add(gv);
      gv.DataBind();
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server" 
      OldValuesParameterFormatString="original_{0}" 
      SelectMethod="GetProductsWithMaterials" 
      TypeName="ProductsDataSetTableAdapters.ProductsTableAdapter">
    </asp:ObjectDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="ObjectDataSource1" 
      OnRowDataBound="GridView1_RowDataBound">
      <Columns>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="MaterialDetails" 
          HeaderText="MaterialDetails" 
          SortExpression="MaterialDetails" />
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

以上、ちょっと凝ったことをしましたが、「製品」:「原料」の関係が 1:1 もしくは 1:n (n=一定) の関係なら、3 つのテーブルを INNER JOIN した SELECT クエリをベースに、SqlDataSource と ListView で表示した方が簡単だと思います。

Tags: , ,

ASP.NET

2 つのテーブルの同時更新

by WebSurfer 2010年8月21日 15:21

SQL Server DB の 2 つのテーブルから、INNER JOIN 句を用いてデータを抽出して GridView に表示し、それを編集・更新 (UPDATE) する Web アプリを考えます。

テーブルが 1 つなら、コードは一行も書かずに SqlDataSource を利用してウィザードベースでアプリを作成できますが、2 つのテーブルを一括更新する場合はそう簡単にはいきません。

それでも、できるだけ自力でコードを書かないで、ObjectDataSource と型付 DataSet + TableAdapter をウィザードベースで作って実現できないか考えて見ました。

ベースは MSDN ライブラリのチュートリアル「Walkthrough: Performing Bulk Updates to Rows Bound to a GridView Web Server Control 」で、そのテーブルを以下の 2 つのテーブルに変更することにします。(チュートリアルには日本語版もあったのですが、リンク切れになってしまいました)

CREATE TABLE [dbo].[table-1](
    [no] int NOT NULL,
    [name] nvarchar(50) NOT NULL,
 CONSTRAINT [PK_table-1] PRIMARY KEY CLUSTERED ([no] ASC)
)

CREATE TABLE [dbo].[table-2](
	[no] int NOT NULL,
	[tel] nvarchar(50) NOT NULL,
	[ban] nvarchar(50) NOT NULL,
 CONSTRAINT [PK_table-2] PRIMARY KEY CLUSTERED ([no] ASC)
)

まず、Visual Studio のウィザードで、型付 DataSet + TableAdapter(xsd ファイル)をつくります。以下のように、クエリビルダを使えば SELECT クエリは簡単に作成でき、それを基にした型付 DataSet も自動生成されます。

クエリビルダ

ただし、ここまでで自動生成された TableAdapter のコードには、更新に必要なメソッドは含まれていません。この先、以下のような方法で、2 つのテーブルを同時に UPDATE するコードを実装します。できるだけ自力でコードを書かないというのが条件です。

xsd ファイルを開いて、その TableAdapter にツールボックスから Query をドラッグ&ドロップ。クエリビルダで tablle-1 を UPDATE するクエリを作り、適当なメソッド名(例: UpdateQuery1)をつけて保存します。

同様に、table-2 を UPDATE するクエリを作り、適当なメソッド名(例:UpdateQuery2)をつけて保存します。結果は、以下のようになるはずです。

xsd ファイル

App_Code フォルダにクラスファイルを追加。UpdateQuery1 と UpdateQuery2 を使って2つのテーブルを UPDATE するメソッドを TableAdapter の partial class として実装します。ここは自力でコードを書く必要があります。以下のようになります。

using System;
using System.Data;
using System.Collections.Generic;
using System.Web;
using System.Transactions;
using System.ComponentModel;

namespace TwoTableDataSetTableAdapters
{
  public partial class DataTable1TableAdapter
  {
    [DataObjectMethod(DataObjectMethodType.Update)]
    public int UpdateTwoTables(int original_no, string name, string tel)
    {
      int returnValue;

      using (TransactionScope scope = 
        new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        returnValue = this.UpdateQuery1(name, original_no);
        returnValue += this.UpdateQuery2(tel, original_no);
        scope.Complete();
      }
      return returnValue;
    }
  }
}

ObjectDataSource の「データソースの構成」で UPDATE メソッドに上記の partial class に作ったメソッドを選択します。

GridView を上記の ObjectDataSource に接続し、参考にした MSDN ライブラリのチュートリアルに従って、Template を編集し、コードを実装します。以下のようになります。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  // http://msdn.microsoft.com/ja-jp/library/aa992036(VS.80).aspx を参照
    
  private bool tableCopied = false;
  private DataTable originalDataTable;

  // 最初の行のバインディング中に、元のデータベース値のコピーが 
  // DataTable オブジェクトに格納され、さらにこのオブジェクトが 
  // ViewState に格納されます。
  protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      if (!tableCopied)
      {
        originalDataTable = 
          ((DataRowView)e.Row.DataItem).Row.Table.Copy();
        ViewState["originalValuesDataTable"] = originalDataTable;
        tableCopied = true;
      }
    }
  }

  protected void UpdateButton_Click(object sender, EventArgs e)
  {
    originalDataTable = (DataTable)ViewState["originalValuesDataTable"];

    // GridView コントロールの行を反復処理し、各行に対してカスタムの 
    // IsRowModified 関数を呼び出します。
    foreach (GridViewRow r in GridView1.Rows)
    {
      if (IsRowModified(r))
      {
        GridView1.UpdateRow(r.RowIndex, false);
      }
    }

    // Rebind the Grid to repopulate the original values table.
    tableCopied = false;
    GridView1.DataBind();
  }

  // このプロシージャは、編集可能な各 TextBox コントロールの値と、
  // キャッシュされた DataTable オブジェクトに格納された値の文字列
  // 比較を実行します。行が変更されている場合は true を返します。
  protected bool IsRowModified(GridViewRow r)
  {
    int currentNo = Convert.ToInt32(GridView1.DataKeys[r.RowIndex].Value);
    string currentName = ((TextBox)r.FindControl("nameTextBox")).Text;
    string currentTel = ((TextBox)r.FindControl("telTextBox")).Text;
    // フィルタ基準と一致するすべての DataRow オブジェクトを主
    // キーの順に(主キーがない場合は追加された順に) 配列として
    // 取得します。
    DataRow row = 
      originalDataTable.Select(String.Format("no = {0}", currentNo))[0];

    if (!currentName.Equals(row["name"].ToString()))
    {
      return true;
    }
    if (!currentTel.Equals(row["tel"].ToString()))
    {
      return true;
    }

    return false;
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>無題のページ</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server" 
      OldValuesParameterFormatString="original_{0}" 
      SelectMethod="GetData" 
      TypeName="TwoTableDataSetTableAdapters.DataTable1TableAdapter" 
      UpdateMethod="UpdateTwoTables">
      <UpdateParameters>
        <asp:Parameter Name="original_no" Type="Int32" />
        <asp:Parameter Name="name" Type="String" />
        <asp:Parameter Name="tel" Type="String" />
      </UpdateParameters>
    </asp:ObjectDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="no" 
      DataSourceID="ObjectDataSource1" 
      onrowdatabound="GridView1_RowDataBound">
      <Columns>
        <asp:BoundField 
          DataField="no" 
          HeaderText="no" 
          ReadOnly="True" 
          SortExpression="no" />
        <asp:TemplateField 
          HeaderText="name" 
          SortExpression="name">
          <ItemTemplate>
            <asp:TextBox ID="nameTextBox" 
              runat="server" 
              Text='<%# Bind("name") %>'>
            </asp:TextBox>
          </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField 
          HeaderText="tel" 
          SortExpression="tel">
          <ItemTemplate>
            <asp:TextBox ID="telTextBox" 
              runat="server" 
              Text='<%# Bind("tel") %>'>
            </asp:TextBox>
          </ItemTemplate>
        </asp:TemplateField>
      </Columns>
    </asp:GridView>    
    <asp:Button ID="UpdateButton" 
      runat="server" 
      onclick="UpdateButton_Click" 
      Text="Update" />    
  </div>
  </form>
</body>
</html>

上記のコードおよび TableAdapter の partial class は自力でコードを書く必要がありますが、その他はすべて ウィザードベースでコードを書かずに実装できるはずです。

実行結果は、以下の画像にようになります。

実行時の画面

Tags: ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar