WebSurfer's Home

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

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

正規表現パターンにコメント

by WebSurfer 2015年7月4日 17:33

.NET Framework の C# のコードで、正規表現のパターンにコメントをつける方法を備忘録として書いておきます。

正規表現のパターンにコメント付与

上の画像を見れば一目瞭然で、それ以上の説明は不要かもしれませんが、それではブログの記事としてはちょっと寂しいので追加情報なども以下に書いておきます。

正規表現については、自分的には MSDN の記事 ASP.NET の正規表現 が一番分かりやすく、いつも参考にさせてもらっています。

おかげさまで、自分でもある程度パターンを作れるようにはなったのですが、自分で作っておきながら後になって読むと意味不明ということがあります。

そういう時のために、正規表現パターンにコメントをつけておくと良いという MDSN の記事を見つけて真似しています。

その記事見つからなくなっ��しまったので、以前自分が書いたコードを探して、それを参考にしてコメントをつけていましたが、探して見つけるのが結構大変ということで、ブログに書いておくことにしました。

以下は完全に余談ですが・・・

例として、パスワードの文字列で、条件として「半角大文字アルファベットと半角数字のみの 4 文字以上、8 文字以下で構成され、それぞれ最低 1 文字を含む」というケースを考えてみます。

そのようなケースでは、上に紹介した「ASP.NET の正規表現」のページの「高度なトピック」のセクションに書いてある "ルックアラウンド処理" を利用するのが便利だと思います。

「正の先読み」および「負の先読み」の両方のケースで書いてみました。前者が下のコードの regex1、後者が regex2 です。

Regex regex1 = new Regex(@"
    ^               # 開始のアンカー
    (?=.*\d)        # 数字が最低 1 文字あること
    (?=.*[A-Z])     # 英大文字が最低 1 文字あること
    [A-Z0-9]{4,8}   # 英大文字または数字が 4 ~ 8 文字
    $               # 終了のアンカー",
    RegexOptions.IgnorePatternWhitespace);

Regex regex2 = new Regex(@"
    (?!^[0-9]*$)    # 全部が数字ということはない
    (?!^[A-Z]*$)    # 全部が英大文字ということはない
    ^               # 開始のアンカー
    ([A-Z0-9]{4,8}) # 英大文字または数字が 4 ~ 8 文字
    $               # 終了のアンカー",
   RegexOptions.IgnorePatternWhitespace);

string[] testStrings = { "AB1", "AB12", "ABCDEF", "123456", 
                         "ABCDEFG1", "1234567A", "ABCD12345", 
                         "ABC123dE", "ABCあ123", "ABC%1234" };

foreach (string s in testStrings)
{
    Console.WriteLine(s + " => " + regex1.IsMatch(s) + " (1)");
    Console.WriteLine(s + " => " + regex2.IsMatch(s) + " (2)");
}

// 結果は:
// AB1 => False (1)
// AB1 => False (2)
// AB12 => True (1)
// AB12 => True (2)
// ABCDEF => False (1)
// ABCDEF => False (2)
// 123456 => False (1)
// 123456 => False (2)
// ABCDEFG1 => True (1)
// ABCDEFG1 => True (2)
// 1234567A => True (1)
// 1234567A => True (2)
// ABCD12345 => False (1)
// ABCD12345 => False (2)
// ABC123dE => False (1)
// ABC123dE => False (2)
// ABCあ123 => False (1)
// ABCあ123 => False (2)
// ABC%1234 => False (1)
// ABC%1234 => False (2)

「正の先読み」というのが (?=<pattern>) という形のもので、対象文字列を先読みしていって <pattern> の条件に合えば true になります。例えば、上のコードでいうと regex1 の (?=.*\d) が該当します。<pattern> は .*\d で、「任意の文字 0 回以上の繰り返しのあと数字がある」という条件になります。

「負の先読み」というのは (?!<pattern>) という形のもので、「正の先読み」の逆すなわち否定になり、<pattern> の条件に合わないものが true になります。上のコードの regex2 の (?!^[0-9]*$) の場合、<pattern> は ^[0-9]*$ で、「文字列の最初から最後まで全ての文字が数字ではない」という条件になります。

後者の方はホントにこれでいいのか自信がないですが(汗)、上のコードの中のコメントに書いたマッチするか否かの結果を見る限りでは、よさそうな感じです。(笑)

Tags:

.NET Framework

canvas の画像をアップロード

by WebSurfer 2015年7月2日 16:27

クライアントの PC にある画像を HTML5 の File API を利用して取得し、それを指定のサイズに縮小して HTML5 の canvas に描画し、canvas に描画された画像を jQuery.Ajax を使って Web サーバーにアップするサンプルを書きます。

2022/11/20 追記: ASP.NET の FileUpload コントロールや jQuery は使わないで、html と JavaScript のみを使って同様な機能を実装した記事を「canvas の画像をアップロード (その 2)」に書きました。

canvas の画像をアップロード

大まかな手順は以下の通りです。詳しくは下のサンプルコードとそれに書いたコメントを参照ください。

  1. クライアントによる画像ファイルの選択は ASP.NET の FileUpload コントロール(普通の HTML の <input type="file" ... /> でも可)を利用する。
  2. FileUpload コントロールで画像ファイルが選択されたタイミングで、HTML5 File API の FileReader オブジェクトに readAsDataURL メソッド を使って選択された画像ファイルを読み込む。
  3. FileReader の result プロパティ を使って、読み込んだ画像ファイルを Data url 形式("data:image/jpeg;base64, ..." という文字列)で取得し、それを image オブジェクトの src 属性に設定する。
  4. その image オブジェクトを HTML5 の canvas に drawImage メソッド を使って描画する。その際、描画する画像の最大サイズの制限を設け(今回のサンプルでは 500 x 500 とした)、それに入る場合はそのまま、入らない場合は幅・高さどちらか大きい方を 500px に縮小し他方をその縮小率と同じに縮小(要するに縦横比を保ったまま 500 x 500 に入るよう縮小)する。
  5. canvas 上の縮小後の画像データを取得して Web サーバーに非同期で送信するメソッドを作る。canvas からの画像データの取得は toDataURL メソッド を用いる。Data url 形式で取得できるので、それを jQuery.Ajax を用いて JSON 形式で送信する。(BASE64 でエンコードされているので、バイナリ形式よりサイズが約 1.3 倍大きくなってしまうが・・・)
  6. <input type="button" ... /> タイプのボタンを配置し、その onclick 属性に上記のメソッドを設定する。
  7. 非同期でクライアントから送信された BASE64 形式の画像データは、.aspx ページに静的メソッドを追加してそれで受け、デコードしてバイナリ形式に戻してファイルに保存する。
  8. オマケとして、画像ファイルのサイズを 500,000 bytes に、ファイルのタイプを image/jpeg に制限した。

サンプルコードは以下の通りです。自分の開発環境の IE9 では File API が使えないので試してませんが、Firefox 38.0.5, Chrome 43.0.2357.130 m, Opera 12.17 で期待通り動くことは確認しました。

同様なコードを 実験室 にアップしましたので、よろしければ実際に動かして試してみてください。画像ファイルのサイズを 500,000 bytes、タイプを image/jpeg に制限しているので注意してください。(実験室ではファイルのアップロードと保存はしていません。代わりに短い文字列を応答として返すようにしています)

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

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

<script runat="server">
  // jQuery.Ajax で POST された画像データを受けるためのメソッ
  // ド。public static にして WebMethodAttribute 属性を付与
  [WebMethod]
  public static string ReceiveImage(string imgBase64)
  {
    // 文字列先頭の "data:image/jpeg;base64," を除去。
    imgBase64 = 
      imgBase64.Replace("data:image/jpeg;base64,", "");

    // BASE64 エンコードされた画像データを元のバイト列に変換
    Byte[] imgByteArray = Convert.FromBase64String(imgBase64);

    // ファイル格納フォルダ FileUploadTest の物理パスを取得
    string savePath = 
      HttpContext.Current.Server.MapPath("~/FileUploadTest/");

    // ファイル名を設定
    string filename = 
      "img" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".jpg";

    // ファイル格納フォルダ FileUploadTest に画像ファイルを保存
    File.WriteAllBytes(savePath + filename, imgByteArray);

    return "ファイル名 " + filename + " として保存しました。";
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Canvas Image Upload</title>
  <script src="Scripts/jquery-1.8.3.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
    // 画像ファイルのサイズ制限を 500,000 bytes、タイプ制限を
    // image/jpeg とする
    var maxFileSize = 500000;
    var allowedContentType = "image/jpeg";

    // アップロードする画像のサイズを 500 x 500 以下とする
    var maxWidth = 500;
    var maxHeight = 500;

    // HTML5 File API の FileReader を利用して FileUpload で
    // 選択された画像ファイルを読み込み、その画像の Data url
    // 形式文字列を取得。それを image オブジェクトの src 属性
    // に設定する。その際 image.onload イベントが発生するので
    // そのイベントにコールバック関数(リスナ)をアタッチ。そ
    // のリスナの中で image オブジェクトが保持する画像をにサ
    // イズ制限以下に縮小して canvas 描画する。以下の 3 行は
    // そのための準備
    var fileReader;
    var image = new Image();
    image.onload = DrawImageOnCanvas;

    // document の読み込み完了後に { } 内の処置を行う
    $(function () {
      // ブラウザの HTML5 File API サポートを確認
      if (window.File && window.FileReader && window.FileList) {
        fileReader = new FileReader();

        // FileReader オブジェクトに画像を読み込むメソッド
        // readAsDataURL は非同期で動くので、読み込み完了の
        // イベントを待ってリスナで処置する必要がある。 
        // readAsDataURL で読み込みが完了すると onloadend 
        // イベントが発生するので、それにリスナをアタッチし、
        // そこで FileReader から Data url を取得し image
        // オブジェクトの src 属性に設定する。
        fileReader.onloadend = function () {
            image.src = fileReader.result;
        };

        // FileUpload1 でファイルの選択が完了すると change
        // イベントが発生するのでそれにリスナをアタッチし、
        // そこで以下の処置を行う。
        $("#<%=FileUpload1.ClientID%>").change(function () {
          var fileUpload =
            document.getElementById("<%=FileUpload1.ClientID%>");

          // ファイルが選択されているか、選択されたファイル
          // のタイプ/サイズは制限に入っているかを確認
          if (ClientValidate(fileUpload) == false) {
              $('#Button1').attr('style', 'display:none');
              return;
          }

          // fileReader オブジェクトに FileUpload1 で選択
          // された画像ファイルを読み込む
          fileReader.readAsDataURL(fileUpload.files[0]);
        });
      }
      else {
        $("#<%=FileUpload1.ClientID%>").
            attr('style', 'display:none');
        $('#mycanvas').attr('style', 'display:none');
        $('#result').text('File API がサポートされてません。');
      }
    });

    // ファイルの確認のためのヘルパ関数
    function ClientValidate(fileUpload) {
        if (fileUpload.files[0] == null) {
            alert("ファイルが未選択です。");
            return false;
        }

        if (fileUpload.files[0].type != allowedContentType) {
            alert("選択されたファイルのタイプが" + 
                allowedContentType + "ではありません。");
            return false;
        }

        if (fileUpload.files[0].size > maxFileSize) {
            alert("ファイルのサイズが制限の " + 
                maxFileSize + " バイトを超えています。");
            return false;
        }

        return true;
      }

      // 上で定義した image オブジェクトの src 属性に Data url
      // が設定されると発生する onload イベントのリスナ。ここ
      // で image 要素から canvas を描画する。
      function DrawImageOnCanvas( )
      {
        // オリジナル画像のサイズ
        var w = image.width;
        var h = image.height;

        var targetW, targetH;
        var context = 
          document.getElementById('mycanvas').getContext('2d');

        if (w <= maxWidth && h <= maxHeight) {
            // w, h ともに制限 maxWidth, maxHeight 以内 ⇒
            // そのままのサイズで canvas に描画
            $('#mycanvas').attr('width', w);
            $('#mycanvas').attr('height', h);
            context.drawImage(image, 0, 0);
        }
        else if (w < h) {
            // w, h どちらかが制限オーバーで h の方が大きい ⇒
            // 高さを maxHeight に縮小
            targetH = maxHeight;
            // 幅は高さの縮小比率で縮小
            targetW = Math.floor(w * targetH / h);
            $('#mycanvas').attr('width', targetW);
            $('#mycanvas').attr('height', targetH);
            context.drawImage(image, 0, 0, targetW, targetH);
        }
        else {
            // w, h どちらかが制限オーバーで w の方が大きい ⇒
            // 幅を maxWidth に縮小
            targetW = maxWidth;
            // 高さは幅の縮小比率で縮小
            targetH = Math.floor(h * targetW / w);
            $('#mycanvas').attr('width', targetW);
            $('#mycanvas').attr('height', targetH);
            context.drawImage(image, 0, 0, targetW, targetH);
        }

        $('#Button1').attr('style', 'display:inline-block');
      }   

      // canvas の画像データを取得して jQuery.Ajax で送信。
      // クライアントからサーバーへ送信できる JSON 文字列の
      // 長さは、デフォルトで 102,400 文字に制限されている。
      // 制限を越える場合 web.config の jsonSerialization 
      // 要素の設定によって変更が必要。
      function uploadImage() {
        var context = 
          document.getElementById('mycanvas').getContext('2d');
        var url = context.canvas.toDataURL("image/jpeg");
            
        $.ajax({
            type: "POST",
            url: "0118-CanvasImageUpload2.aspx/ReceiveImage",
            data: '{"imgBase64":"' + url + '"}',
            contentType: "application/json; charset=utf-8",

            success: function (data) {
                // .NET 3.5 で追加された d パラメータの処置
                if (data.hasOwnProperty('d')) {
                    data = data.d;
                }
                $('#result').text(data);
            },

            error: function (jqXHR, textStatus, errorThrown) {
                $('#result').text('textStatus: ' + textStatus +
                       ', errorThrown: ' + errorThrown);
            }
          });
      }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:FileUpload ID="FileUpload1" runat="server" />
    <input id="Button1" type="button" value="Upload" 
        style="display:none;"
        onclick="javascript:uploadImage();" />
    <br />
    <canvas id="mycanvas" width="500" height="500"></canvas>
    <div id="result"></div>
  </div>
  </form>
</body>
</html>

上のコメントにも書きましたが、クライアントからサーバーへ送信できる JSON 文字列の長さは、デフォルトで 102,400 文字に制限されています。制限を越える場合 web.config の jsonSerialization 要素の設定によって変更が必要となりますので注意してください。以下のように設定します。(数字 300000 は実際にあわせて変更してください)

<configuration>
  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="300000"/>
      </webServices>
    </scripting>
  </system.web.extensions>
</configuration>

Tags: ,

Upload Download

About this blog

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

Calendar

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

View posts in large calendar