WebSurfer's Home

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

DropDownList での NULL の処置

by WebSurfer 2015年10月23日 17:19

下の画像のように、GridView などでデータベースの更新操作を行う際 DropDownList を利用してユーザー入力に便宜を図ることがあると思います。その時、NULL を DropDownList でどう対処するかという話を書きます。

GridView 上の DropDownList

ここで紹介する例には Microsoft が提供している Northwind サンプルデータベースの Products テーブルと Categories テーブルを使用しています。

Products テーブルの中の ProductName, CategoryID フィールドを GridView 上で更新する際、CategoryID の列に DropDownList を表示するようにします。

DropDownList には、ユーザーが見ても何だか分からない数字 (CategoryID) を表示するのでははなくて、ユーザーが読んで理解できる名前 (CategoryName) を Categories テーブルから取得して表示します。

Products テーブルの CategoryID には以下のような外部キー制約がかかっていますので、Categories テーブルの CategoryID の値(Northwind サンプルデータベースでは 1 から 8)以外のものは入力できません。

ALTER TABLE [dbo].[Products]  
  ADD CONSTRAINT [FK_Products_Categories] 
  FOREIGN KEY([CategoryID])
  REFERENCES [dbo].[Categories] 
  ([CategoryID])

ただし、NULL は許可されていますので、1 から 8 以外に NULL が入っていることがあります。また、更新の際に NULL を入力したいというケースもあるかもしれません。

先の記事 GridView 上の DropDownList に ToolTip でも同様に DropDownList を使用していますが、そこでは NULL 対応は考えていませんでした。それを以下ように修正して NULL に対応します。

  1. SELECT クエリで INNER JOIN 句に替えて LEFT OUTER JOIN 句を使い CategoryID が NULL のレコードも抽出する。
  2. DropDownList で AppendDataBoundItems="True" とし、以下のような NULL 用の ListItem を追加する。

    <asp:ListItem Value="">NULL</asp:ListItem>

    Value は空白("")にしてください。DropDownList に表示される文字列 NULL は任意のものに変えていいです。データベース上での NULL と ASP.NET コントロール上での空白("")の変換は、ASP.NET の組み込み機能を利用します。その機能についての説明は MSDN ライブラリの記事データ ソース コントロールを使用した、データベースの Null 値の処理を見てください。

具体的なコードは以下の通りです。(分かりやすくするため、先の記事 GridView 上の DropDownList に ToolTip のコードの ToolTip を設定する部分は省いています)

<%@ Page Language="C#" %>

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

<script runat="server">
    
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind2 %>" 
      SelectCommand=
        "SELECT TOP 10 
        p.[ProductID], p.[ProductName], 
        p.[CategoryID], c.[CategoryName] 
        FROM [Products] AS p 
        LEFT OUTER JOIN [Categories] AS c 
        ON p.[CategoryID] = c.[CategoryID]
        ORDER BY p.[ProductID]" 
      UpdateCommand=
        "UPDATE [Products] 
        SET [ProductName] = @ProductName, 
        [CategoryID] = @CategoryID 
        WHERE [ProductID] = @ProductID">
      <UpdateParameters>
        <asp:Parameter Name="ProductID" Type="Int32" />
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="CategoryID" Type="Int32" />
      </UpdateParameters>
    </asp:SqlDataSource>

    <asp:SqlDataSource ID="SqlDataSource2" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind2 %>" 
      SelectCommand=
        "SELECT [CategoryID], [CategoryName], [Description] 
        FROM [Categories]">
    </asp:SqlDataSource>
        
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="SqlDataSource1">
      <Columns>
        <asp:CommandField ShowEditButton="True" />
        <asp:BoundField DataField="ProductID" 
          HeaderText="ID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="Product Name" 
          SortExpression="ProductName" />
        <asp:TemplateField HeaderText="Category" 
          SortExpression="CategoryName">
          <EditItemTemplate>                        
            <asp:DropDownList ID="DropDownList1" 
              runat="server" 
              DataSourceID="SqlDataSource2" 
              DataTextField="CategoryName" 
              DataValueField="CategoryID" 
              SelectedValue='<%# Bind("CategoryID") %>' 
              AppendDataBoundItems="True">
              <asp:ListItem Value="">NULL</asp:ListItem>
            </asp:DropDownList>
          </EditItemTemplate>
         <ItemTemplate>
            <asp:Label ID="Label1" 
              runat="server" 
              Text='<%# Bind("CategoryName") %>'>
            </asp:Label>
          </ItemTemplate>
        </asp:TemplateField>
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

Tags: , ,

ASP.NET

GridView の最終ページに空行追加

by WebSurfer 2015年7月7日 16:14

GridView と SqlDataSource を使用してページングを行う際、最終ページの行数が PageSize より少ない場合、最終データ行の次に即ページャー行が表示されます。(下の画像のように空行は表示されません)

最終ページに空行を挿入

それを、上の画像のように空行を挿入(例えば、PageSize が 10 で、最終ページのデータ行が 7 行の場合、空のデータ行を 3 行追加する)する方法を書きます。

自力でコードを書いて DataTable を作って、それを GridView にデータバインドしているような場合は、DataTable に空行を追加するのが簡単そうですが、GridView と SqlDataSource を組み合わせて使っているような場合はその手が使えません。(ウラワザ的なことをすれば話は別かもしれませんが)

なので、DataTable を細工するのは諦めて、GridView.RowCreated イベントでデータ行の行数を調べ、それが PageSize より少ない場合は空の GridViewRow を追加する方法を取ってみました。

簡単に書くと、(1) ヘッダ行で RowCreated イベントが発生した際にそのセル数を取得。[ヘッダ行のセル数] = [データ行のセル数] というのが前提です、(2) ページャー行で RowCreated イベントが発生した際、[データ行] < [PageSize] であればその差の分空のデータ行を生成し GridView に追加する・・・ということです。

そのコードは以下のようになります。コメントに上記 (1), (2) の詳細を書きましたので見てください。データベースは、Microsoft が提供しているサンプル Northwind の Products テーブルを使っています。

<%@ Page Language="C#" %>

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

<script runat="server">

  // データ行のセル数
  int numberOfCells = 0;
    
  protected void GridView1_RowCreated(object sender, 
                                      GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.Header)
    {
      // ヘッダ行のセル数=データ行のセル数が前提
      numberOfCells = e.Row.Cells.Count;
    }        
    else if (e.Row.RowType == DataControlRowType.DataRow)
    {
      // データ行では何もしない
    }
    else if (e.Row.RowType == DataControlRowType.Footer)
    {
      // フッタ行でも何もしない。ページャー行よりこち
      // らの方のイベント発生が先になるが
    }
    else if (e.Row.RowType == DataControlRowType.Pager)
    {
      // AllowPaging="False" に設定されているとここに
      // 制御は飛んでこない。なので、データ行追加の小
      // 細工はここで行った方がよさそう。
            
      GridView gridView = (GridView)sender;
      int numberOfRows = gridView.Rows.Count;
      int pageSize = gridView.PageSize;

      // データ行 < PageSize の場合、その差の分だけ
      // 空のデータ行とその中身(空セル)を生成
      for (int i = numberOfRows; i < pageSize; i++)
      {
        GridViewRow row = new GridViewRow(
                i,
                -1,
                DataControlRowType.DataRow,
                DataControlRowState.Normal);

        for (int j = 0; j < numberOfCells; j++)
        {
          TableCell cell = new TableCell();
          row.Cells.Add(cell);
        }

        // 空のデータ行を GridView に追加。
        // Collection の中の順番は Footer/Pager 行の
        // 後になるが、HTML にレンダリングされる時は
        // 期待した順序になる。上の GridViewRow のコ
        // ンストラクタで設定した i (RowIndex) と
        // DataControlRowType.DataRow を見ている?
        gridView.Controls[0].Controls.Add(row);
      }           
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <style type="text/css">
      table tr
      {
        /* 行(空行を含む)の高さ設定 */
        height: 30px;
      }    
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand="SELECT * FROM [Products]">
    </asp:SqlDataSource>

    <asp:GridView ID="GridView1" runat="server" 
      AllowPaging="True" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="SqlDataSource1" 
      OnRowCreated="GridView1_RowCreated">
      <Columns>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" InsertVisible="False" 
          ReadOnly="True" SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="SupplierID" 
          HeaderText="SupplierID" 
          SortExpression="SupplierID" />
        <asp:BoundField DataField="CategoryID" 
          HeaderText="CategoryID" 
          SortExpression="CategoryID" />
        <asp:BoundField DataField="QuantityPerUnit" 
          HeaderText="QuantityPerUnit" 
          SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock" 
          HeaderText="UnitsInStock" 
          SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder" 
          HeaderText="UnitsOnOrder" 
          SortExpression="UnitsOnOrder" />
        <asp:BoundField DataField="ReorderLevel" 
          HeaderText="ReorderLevel" 
          SortExpression="ReorderLevel" />
        <asp:CheckBoxField DataField="Discontinued" 
          HeaderText="Discontinued" 
          SortExpression="Discontinued" />
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

Tags:

Paging

GridView の Pager に総レコード数を表示

by WebSurfer 2014年12月6日 18:26

以下の画像のように、ページングが有効になっている GridView の Pager 内に「総レコード数」を表示するにはどうしたらいいかということを書きます。

Pager に総レコード数を表示

ListView と DataPager を利用する場合は、先の記事 ListView でページ指定 のサンプルコードのように Container.TotalRowCount で総レコード数が取得できます。(注:Container は DataPager オブジェクトを参照しています)

しかしながら、GirdView を利用した場合、ページング機能は GirdView に統合されているものを利用することになり、それから「総レコード数」を取得するプロパティなどはありません。

GridView は、データーソースコントロール(SqlDataSource または ObjectDataSource)と組み合わせて使うケースがほとんどだと思いますが、その場合はデーターソースコントロールの Selected イベントのハンドラを使って「総レコード数」を取得するのが簡単そうです。

例えば、データーソースコントロールに SqlDataSource を使う場合、その Selected イベントのハンドラの引数 SqlDataSourceStatusEventArgs クラスAffectedRows プロパティ から総レコード数を取得できます。

取得した「総レコード数」をページャーの中に表示するには、GridView.RowDataBound イベント のハンドラでページャーの行を探して、その中に追加します。

ページャーそのものはデフォルトで(何も設定しなくても) 1 2 3 4 5 6 7 8 9 10 ... というようなクリックするとその番号のページに飛べるハイパーリンクが生成されますが、PagerSettings クラス を使うともう少し細かな設定ができます。

上の画像のページャーは、PagerSettings クラスの Mode プロパティを NumericFirstLast に、PageButtonCount プロパティを 5 に設定した場合のものです。

SqlDataSource と GridView を使った場合のサンプル(上の画像のページを表示したもの)を以下にアップしておきます。

<%@ Page Language="C#" %>

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

<script runat="server">

  int GridView3TotalCount = 0;
    
  protected void SqlDataSource1_Selected(
        object sender, SqlDataSourceStatusEventArgs e)
  {
      GridView3TotalCount = e.AffectedRows;
  }

  protected void GridView3_RowDataBound(
        object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.Pager)
    {
      Label label = new Label();
      label.Text = "総レコード数: " + 
            GridView3TotalCount.ToString();
      e.Row.Cells[0].Controls.AddAt(0, label);
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>GridView Total Records</title>
</head>
<body>
  <form id="form1" runat="server">
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand="SELECT * FROM [Orders]" 
      OnSelected="SqlDataSource1_Selected">
    </asp:SqlDataSource>

    総レコード数: <%=GridView3TotalCount%>
    <asp:GridView ID="GridView3" runat="server" 
      AllowPaging="True" 
      AutoGenerateColumns="False" 
      DataKeyNames="OrderID" 
      DataSourceID="SqlDataSource1" 
      OnRowDataBound="GridView3_RowDataBound">
      <PagerSettings 
        Mode="NumericFirstLast" 
        PageButtonCount="5">
      </PagerSettings>
      <Columns>
        <asp:BoundField DataField="OrderID" 
          HeaderText="OrderID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="OrderID" />
        <asp:BoundField DataField="CustomerID" 
          HeaderText="CustomerID" 
          SortExpression="CustomerID" />
        <asp:BoundField DataField="OrderDate" 
          HeaderText="OrderDate" 
          SortExpression="OrderDate" 
          DataFormatString="{0:yyyy/MM/dd}" />
        <asp:BoundField DataField="Freight" 
          HeaderText="Freight" 
          SortExpression="Freight" 
          DataFormatString="${0:N2}" />
        <asp:BoundField DataField="ShipCountry" 
          HeaderText="ShipCountry" 
          SortExpression="ShipCountry" />
      </Columns>
    </asp:GridView>
  </form>
</body>
</html>

データーソースコントロールに ObjectDataSource を使う場合も、ObjectDataSource.Selected イベントのハンドラ で「総レコード数」を取得できます。

ObjectDataSource を使う場合、ページを切り替えるたびに全レコードを DB から再度取得するという無駄なことを避けるため、普通は、先の記事 ObjectDataSource でページング に書いてあるように、必要なレコードのみ DB から取得するクラス/メソッドを実装して、それらを ObjectDataSource で定義されているプロパティに設定することになると思います。

上に紹介した記事のサンプルコードでは、GetDataByIndex と GetNumberOfMessages の 2 つのメソッドが ObjectDataSource に設定されていますが、その両方が呼ばれ、それぞれで Selected イベントが発生します(2 回 Selected イベントが発生します)。

その際、イベントハンドラの引数 ObjectDataSourceStatusEventArgs の ReturnValue プロパティ でそれぞれのメソッドが返すオブジェクトを取得できます。

したがって、e.ReturnValue が Int32 型にキャストできれば GetNumberOfMessages が返したもの、即ち全レコード数ということになります。なので、イベントハンドラのコードを以下のようにすれば「総レコード数」を取得できます。

protected void ObjectDataSource1_Selected(object sender, 
                        ObjectDataSourceStatusEventArgs e)
{
  if (e.ReturnValue is Int32)
  {
    GridView3TotalCount = (Int32)e.ReturnValue;
  }
}

「総レコード数」さえ取得できれば、あとは GridView と Pager の実装を SqlDataSource を使った場合と同様にして Pager に表示できます。

Tags: ,

Paging

About this blog

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

Calendar

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

View posts in large calendar