QA@IT

ASP.NETのグリッドビューのチェックボックスについて、イメージの使用や、空白カラム、ソートについて質問

9170 PV

ASP.Xファイル

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" 
        DataKeyNames="t1" DataSourceID="SqlDataSource1" AllowSorting="True" Height="115px" 
        onrowcreated="GridView1_RowCreated" Width="87px">
        <Columns>
            <asp:BoundField DataField="t1" HeaderText="t1" ReadOnly="True" 
                SortExpression="t1" />
            <asp:BoundField DataField="chStr" HeaderText="chStr" SortExpression="chStr" />
            <asp:BoundField DataField="ch" HeaderText="ch" SortExpression="ch" />
            <asp:CheckBoxField DataField="chBit" HeaderText="chBit" 
                SortExpression="chBit" />
        </Columns>
</asp:GridView>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ConnectionString="<%$ ConnectionStrings:testdbConnectionString %>" 
        SelectCommand="SELECT [t1], [chStr], [ch], [chBit] FROM [tbl001]">
</asp:SqlDataSource>

csファイル

protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowIndex >= 0) {
        var cb = (CheckBox)e.Row.Cells[3].Controls[0];
        cb.Enabled = true;
    }
}

データの内容(chBitはyes/no型)

t1|t2                     |t3 |t4                     |ch  |chStr|chBit
-----------------------------------------------------------------------
2 |適当な文字列を入れてね2|aaa|2013-04-04 17:20:09.460|1   |true |False
3 |適当な文字列を入れてね3|aaa|2013-04-04 17:20:09.460|0   |false|True
4 |適当な文字列を入れてね4|aaa|2013-04-04 17:20:09.460|NULL|true |False
5 |適当な文字列を入れてね5|aaa|2013-04-04 17:20:09.460|NULL|false|True
6 |適当な文字列を入れてね6|aaa|2013-04-04 17:20:09.463|NULL|NULL |NULL
チェックボックスはchBitに結びつけられています

というような、WEBフォームを作ったとき、
チェックボックスを使ってやりたいことが2種類あるのですが、なかなかできずに困っています

1.通常のチェックボックスではなく、イメージにしたい
 つまり、オリジナルのイメージで、チェックしていない状態と、チェックした状態を実現したい
 ボタンの押されている状態と、押されていない状態のイメージを設定したいのですがどうしたらいいのか?
2.データがNULLの時は表示したくない
 chBitにはNULLが設定されることが有り、そのときはカラムを空欄にしたい
3.ヘッダをクリックされたときにソートするが、クリックするたびにデータベースを読んでしまい
 チェックボックスを変更した状態がキャンセルされてしまうので、
 この項目に関しては、ヘッダクリックのソートはデータベースの再読込ではなく、
 すでに読み込んだ状態でソートしたい

ASP.NETが、よくわかってないのでもしかしたら、簡単に実現出来てしまうのかもしれないですが・・よろしくお願いします
チェックボックスではなく、GridView上の他のコントロールで実現出来るのでしたらそちらで教えてください

回答

質問者さん>

一つ忘れてました。

3 のデータベースから再取得せずにソートですが、自分のブログで恐縮ですが、以下のページで紹介しているように JavaScript のライブラリを使う方法があります。

GridView のヘッダ、列を固定(その 2)
http://surferonwww.info/BlogEngine/post/2013/02/04/freezing-header-and-column-of-gridview-by-using-javascript-gridjs.aspx

ただし、そもそもかなり面倒(無理)なことですし、1, 2 を同時に実現できるかはやってみないと分からないです。さらに、編集・更新まで実装するのは多分無理だと思います。

他にも、JavaScript を使って table をソートする例は、javascript table sort 等で検索するといろいろ見つかると思います。

例えば下記:

JavaScript Table Sorter
http://www.scriptiny.com/2008/11/javascript-table-sorter/

ソートできるテーブルを実装するスクリプト10選
http://coliss.com/articles/build-websites/operation/javascript/792.html

質問者さんの目的に適うものが見つかるかどうかは分かりませんが、そのあたりはご自分で検討して下さい。

以上、ご参考まで。

編集 履歴 (1)
  • すいません。いろいろありがとうございます
    今出先で試せないのですが、会社に戻ったら教えていただいた情報を元に、試してみます
    だめだったらだめで、先方に伝えてみようと思います

    -

閉じたようですが、こういうやり方もあるという事で一応残しておきます。

画像チェックボックス

画像のチェックボックスの部分は
http://stackoverflow.com/questions/16352864/how-to-display-image-in-place-of-checkbox
を参考にさせてもらいました(画像リソースも上記サイトのものを借りてしまっていますので、必要に応じてpngファイルをローカルに保存するなどしてください)。

ソート部分

1点 注意として、ここではサーバーに返らずにソートする部分を簡易に実現するためにGridViewの
EnableSortingAndPagingCallbacks="true"AllowSorting="true"を使って実現しています。
このため、ソート時に表内のセルのIDの振り直しとセルの内容の復元(ページ表示時の状態に戻る)が起こります。
IDの振り直しは レコードのキーを追加 しておいて対応していますが、ソートするとチェック状態が戻ってしまいます(サーバーにはアクセスしていません)。これは javascript でソートする様になれば回避できる問題ですので申し訳ないですがサンプルの簡易化のためにこうさせてもらっています。

javascriptによるソートに関してですが、GridViewに特化したものもあるようです。

http://www.codeproject.com/Articles/30287/Sorting-Gridview-using-Jquery-with-ASP-NET

http://www.codeproject.com/Articles/30011/Sortable-GridView-using-jQuery-s-TableSorter

どちらが良いというわけではなくただの補足です。


チェック状態のサーバーへの返却部分について

このサンプルでは動的にGridViewに追加したコントロールをボタンが押された時のPostBack時に、Request.Formから探して情報を取り出しています。
ASP.NETコントロールとしてというよりは、HTMLのエレメントとして取り出しています。

これ以外ではaspx側でjavascriptを使ってあらかじめ配置していた Hiddenコントロールに後で解析できるように値を詰めてPostBack時に分解する方法や、
Page.RegisterHiddenFieldで hiddenフィールド必要な分だけ作成してPostBack時にRequest.Formから取り出すといった方法もあります。(RegisterHiddenFieldの方が作成した時に指定した名前(name属性)を後でそのまま使えるのできれいにかけるかもしれません)

サンプル

前置きが長くなりましたが以下がサンプルコードです。
チェックを付け外しして、ボタンを押すとバインドされているチェックと比べてどのように変化したかがテキストエリアに表示されます。
( キー: 変化した値 <== 元の値 という形で出ます。)

テーブル名、列名、データソース名は合わせたつもりです。ただしselectしている列の数は違いますのでお気を付けください。

WebForm1.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="QaAtItWeb.WebForm1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <style>
        /* http://stackoverflow.com/questions/16352864/how-to-display-image-in-place-of-checkbox を利用させてもらっています */
        .checkbox{
            width: 23px;
            height: 21px;
             background: transparent url(http://i.stack.imgur.com/S4p2R.png ) no-repeat 0 50%;
        }
        .checked{
            background: transparent url(http://i.stack.imgur.com/S4p2R.png ) no-repeat 80% 50%;
        }


        span.checkbox input[type="checkbox"] {
            visibility: hidden;
        }
        input[type="text"].checkbox {
            visibility: hidden;
        }
    </style>
    <!-- jqueryのモジュールは googleから取得しています -->
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script>
        $(document).ready(function () {
            //ページ読み込み後の処理


            // ViewStateによりページの状態が復元されるのでそれに合わせてイメージチェックボックスの状態も同期させます。

            $('span.checkbox input[type="checkbox"]').each(function () {
                if ($(this).prop("checked")) {
                    $(this).closest("span").addClass("checked");
                } else {
                    $(this).closest("span").removeClass("checked");
                }
            });


            // イメージチェックボックスのクリックイベント
            $("form").on("click", "span.checkbox", function () {
                $(this).toggleClass('checked')
                if ($(this).hasClass('checked')) {
                    $("input", this).prop('checked', true);
                } else {
                    $("input", this).prop('checked', false);
                }
            });

        });

    </script>
</head>
<body>

    <form id="form1" runat="server">
    <div> 
        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="t1" DataSourceID="SqlDataSource1" AllowSorting="true" EnableSortingAndPagingCallbacks="true" OnRowCreated="GridView1_RowCreated">
            <Columns>
                <asp:BoundField DataField="t1" HeaderText="t1" ReadOnly="True" SortExpression="t1" />
                <asp:BoundField DataField="t2" HeaderText="t2" SortExpression="t2" />
                <asp:BoundField DataField="ch" HeaderText="ch" SortExpression="ch" />
                <asp:CheckBoxField HeaderText="ImgCheck" SortExpression="chBit" />
                <asp:CheckBoxField DataField="chBit" HeaderText="元の値" SortExpression="chBit" />
            </Columns>
        </asp:GridView>
        <asp:Button ID="Button1" runat="server" Text="Button" /><br />
        <asp:TextBox ID="TextBox1" runat="server" TextMode="MultiLine" Height="100px"></asp:TextBox>

        <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:testdbConnectionString %>" SelectCommand="SELECT [t1], [t2], [ch], [chBit] FROM [tbl001]"></asp:SqlDataSource>

    </div>
    </form>

</body>
</html>

WebForm1.aspx.cs

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

namespace QaAtItWeb
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

            if(IsPostBack){
                TextBox1.Text = "";

                foreach (GridViewRow r in GridView1.Rows)
                {
                    // チェックボックスの変化を表示
                    // 実際にはここで取得するような情報でDBの更新などを行えばよいと思います。



                    // ループ中に検索しているのでよくないコードですが、コードが長くならないようにこうしています。

                    var key = Page.Request.Form.AllKeys
                                .Where(x => x.EndsWith("checkbox_key_" + r.RowIndex.ToString()))
                                .FirstOrDefault();

                    var val=Page.Request.Form.AllKeys
                                .Where(x => x.EndsWith("checkbox_" + r.RowIndex.ToString()))
                                .FirstOrDefault();

                    // 状態の初期値
                    var newState = "off";

                    if (val != null) {
                        newState = Request.Form[val];
                    }

                    TextBox1.Text +=
                        String.Format("{0} :  {2}  <==  {1} \r\n",
                            Request.Form[key],
                            ((CheckBox)r.Cells[4].Controls[0]).Checked,
                            newState);

                }

                GridView1.DataBind();
            }


        }

        protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
        {

            if (e.Row.RowIndex >= 0)
            {
                DataRowView d = (DataRowView)e.Row.DataItem;
                if (d == null) return;


                // イメージチェックボックス用のオブジェクトの追加

                if (d.Row["chBit"] != DBNull.Value)
                {
                    var cb = new CheckBox();
                    // IDを自分で設定しておき、POSTBACK時に取得する際に利用する。
                    cb.ID = "checkbox_" + e.Row.RowIndex.ToString();
                    e.Row.Cells[3].Controls.Add(cb);

                    if ((bool)d.Row["chBit"] == true)
                    {
                        cb.Checked = true;
                        cb.CssClass = "checkbox checked";
                    }
                    else
                    {
                        cb.Checked = false;
                        cb.CssClass = "checkbox";
                    }
                }

                // レコードのキーを追加 ( EnableSortingAndPagingCallbacksでソートしているために )
                var keytext = new TextBox();
                keytext.ID = "checkbox_key_" + e.Row.RowIndex.ToString();

                keytext.Text = d.Row["t1"].ToString();
                keytext.CssClass = "checkbox";
                e.Row.Cells[3].Controls.Add(keytext);

            }
        }

    }
}

以上、ご参考までに。

編集 履歴 (1)
  • すいません。全然気づきませんでした。
    チェックボックスはjQueryでググって、解決しました。
    後から参考に書いてもらった方はまだ試していません

    ソートに関しては今決定待ちの状態ですが、jQueryでいくつか試している状態です(他のプラグインがあっても全然平気なものと、全く受け付けなくなってしまうものといろいろあるようです)
    -
  • 個人的にはtablefixという日本人の方が作ったものが見栄えが一番いいですが、他のプラグインとの相性も一番悪いみたいでした・・・ -

最終的にチェックされている情報をどのようにサーバーに返すかという問題も出てきますが、そのあたりは調べてみてください。

そこが一番問題だと思いますが、それに指針を示さず「そのあたりは調べてみてください」というのはいかがなものかと思いますけど。それなら無理だと言った方がよさそうですが。

編集 履歴 (1)
  • 別hiddenコントロールやにつめればいいだけで大した問題ではありませんよ。他にも手はあります。
    何をもって無理無理言っているのかわかりません。
    -
  • 質問者さんの言われる 1, 2, 3 全てを無理なくどう実現するか具体的に示していただけますか? 1 は表示するだけでなく、クリックで状態が変わり、たぶん更新もかけられるようにするのだと思いますので、そのあたりもよろしくお願いします。 -

素のASP.NET(というかWebFormのサーバーコントロール)はPostBackやViewStateでステートフルであろうとします。

その状態更新のために頻繁にサーバー通信を行います。
たとえばチェックボックスであれば、チェックの付け外しのたびにサーバーに通信して状態を更新します。
配置したイメージボタン(サーバーコントロール runat="server"になってると思います)をクリックするたびに再読み込みされるのは同じ理由です。

一方であなたの今のコードは再読み込みされると再度データベースからデータを取得して、GridViewを再作成してしまいます。ソート処理もASP.NETのソート機能を利用しているため、ソートの度に再読み込みされ、やはりGridViewを再作成しています。

これが今起こっていることです。


実装するには

  • イメージボタンを押した際のポストバックでイメージボタンの状態を保管しておいて、GridViewの書き込みの際に利用する。

  • GridView 1ページに収まるのであればソート処理も画像のチェックボックスもASP.NETの機能ではなく、HTML+CSS+javascriptで実装する。

等の方法が考えられます。
イメージボタンの状態を保管しておく方法では状態をどのように保管して置くかがカギになります。一時表や連想配列やViewStateなどいろいろあるとは思いますが、複数ユーザーのアクセスがあることを踏まえたうえでなるべくリソースを圧迫しないものを検討されるといいでしょう。
HTML+CSS+javascriptの方法ではイメージコントロールのようなASP.NETサーバーコントロールを使用しません。最終的にチェックされている情報をどのようにサーバーに返すかという問題も出てきますが、そのあたりは調べてみてください。

編集 履歴 (0)

すいません。
1.に関してはjQuery等を参照してみます
3.に関してですが、
 ステートレスというのは、ブラウザが自分の状態を覚えているわけではない
 ということと考えていますが違っているのでしょうか

 チェックボックスに関しては、初回以降はヘッダをクリックすると、
 データベースにアクセスせず、
 自分でチェックしたりした状態を何とか覚えられないかという都合のいいこと(多分ここがステートレスではなくなってしまうので)を考えていたのですが、
 無理そうでしたら諦めます

編集 履歴 (0)
  • > 無理そうでしたら諦めます
    あまり詳しくなさそうですので、もともと ASP.NET に備わっている機能以上のことをするのは難しいと思います。
    -

1.に関しては、クリックしたらチェックの状態を変化させたいです

であれば、JavaScript, jQuery を使って何とかするほかなさそうです。jquery checkbox image 等をキーワードにググるといろいろ参考になるページが見つかると思います。

3.は無理すればできないこともない・・・というのはかなりやっかいと言うことでしょうか

もう一回言いますが、Web アプリはステートレスということを理解してますか? 理解してないからそういう発想が出てくるのだと思いますけど。

あと、クライアント側で起こっていることとサーバー側で起こっていることの違いを理解しましょう。そのあたりの理解がないと掲示板では話が通じません。

編集 履歴 (0)

ありがとうございます
質問の書き方が悪かったです
1.に関しては、クリックしたらチェックの状態を変化させたいです
イメージボタンがあったのでそちらで試してみたのですが、
イメージボタンをクリックしたときに、クリックした時に押し込んだ状態のイメージにしようと思ったのですが、
クリックしたら、再度読み出してしまいました

aspxファイルのGridViewにテンプレートフィールドを追加した

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="t1" DataSourceID="SqlDataSource1" AllowSorting="True" Height="115px" 
    onrowcreated="GridView1_RowCreated" Width="87px">
    <Columns>
        <asp:BoundField DataField="t1" HeaderText="t1" ReadOnly="True" 
            SortExpression="t1" />
        <asp:BoundField DataField="chStr" HeaderText="chStr" SortExpression="chStr" />
        <asp:BoundField DataField="ch" HeaderText="ch" SortExpression="ch" />
        <asp:CheckBoxField DataField="chBit" HeaderText="chBit" 
            SortExpression="chBit" />
        <asp:TemplateField SortExpression="chBit" HeaderText="chBitTmplt">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" Text='<%# Eval("chStr") %>'></asp:Label>
                <asp:Image ID="Image1" runat="server" ImageUrl="~/img/check_no.bmp" />
                <asp:ImageButton ID="ImageButton1" runat="server" 
                    ImageUrl="~/img/check_no.bmp" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

csファイルRowCreatedにイメージ入れ替え処理追加

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    //データ内容によって、チェックボックスを変化させる
    DataRowView d1 = (DataRowView)e.Row.DataItem;   //グリッドビューに結びついているデータ                

    if (d1 != null) {                               
        var d = d1.Row["chBit"];                    //chBitの状態をチェック
        int iCnt = e.Row.Cells[4].Controls.Count;
        //イメージで
        for (int i = 0; i < iCnt; i++) {
            //イメージボタン
            if (e.Row.Cells[4].Controls[i].ID == "ImageButton1") {
                ImageButton ib = (ImageButton)e.Row.Cells[4].Controls[i];
                if (d == null) {
                    ib.Visible = false;
                } else if (d is DBNull) {
                    ib.Visible = false;
                } else if ((bool)d == false) {
                    ib.ImageUrl = "~/img/check_act.bmp";
                }
            }
            //イメージコントロール
            if (e.Row.Cells[4].Controls[i].ID == "Image1") {
                Image img = (Image)e.Row.Cells[4].Controls[i];
                if (d == null) {
                    img.Visible = false;
                } else if (d is DBNull) {
                    img.Visible = false;
                } else if ((bool)d == false) {
                    img.ImageUrl = "~/img/check_act.bmp";
                }
            }
        }
    }
}

2.に関してはとりあえず、教えていただいたように1.と同様の処理にしています
3.は無理すればできないこともない・・・というのはかなりやっかいと言うことでしょうか
  データベースの値を表示するコントロールが、
  都合のいいときだけ画面の値で動作しろというのは確かに虫が良すぎるかもしれないので、
  ここだけソートを無しにできないか検討してみます
  
  ただ、他の項目でソートしたときも、再度データベースから読み込んでしまうので
  できれば、この項目だけは2回目以降は画面から操作された状態を保持してはみたいと思っています
  このために、一時テーブルを作る・・・とかで回避出来るかもしれませんが

・EditItemTemplateというのが、テンプレートフィールドにあるので、もうちょっといじってみます

編集 履歴 (0)

表示するだけ(編集は不要)でいいのですよね? であれば、以下のようにしてはいかがですか?

1.通常のチェックボックスではなく、イメージにしたい

CheckBoxField の代わりに TemplateField を使い、その中に Image コントロールを配置。GridView.RowDataBound イベントで chBit を調べ、その結果に応じてイメージを差し替える。

2.データがNULLの時は表示したくない

上記 1 と同様、GridView.RowDataBound イベントで chBit を調べ、NULL の時は当該セルの Text プロパティを String.Empty に書き換える。

3.ヘッダをクリックされたときにソートするが・・・すでに読み込んだ状態でソートしたい

「すでに読み込んだ状態」と言ってますが、どこにどういう形で読み込んで保持していると思っていますか? Web アプリはステートレスということを理解しているでしょうか?

無理してできないことはないかもしれないと言ったレベルの話で、そこまで無理する必要があるかをまず考えた方がいいと思います。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。