並べ替えられたデータの長い一覧を表示する場合は、区切り記号行を導入して関連するデータをグループ化すると非常に役立ちます。 このチュートリアルでは、このような並べ替えユーザー インターフェイスを作成する方法について説明します。
イントロダクション
並べ替えられた列に少数の異なる値しかない並べ替えられたデータの長い一覧を表示する場合、エンド ユーザーは、違いの境界が発生する場所を正確に識別するのが難しい場合があります。 たとえば、データベースには 81 個の製品がありますが、カテゴリの選択肢は 9 つだけです (8 つの一意のカテゴリと NULL
オプション)。 シーフードカテゴリに該当する製品を調べることに関心があるユーザーの場合を考えてみましょう。 1 つの GridView 内のすべての 製品を一覧表示するページから、ユーザーは、すべてのシーフード製品をまとめてグループ化するカテゴリ別に結果を並べ替えることのベスト ベットを決定する場合があります。 カテゴリ別に並べ替えた後、ユーザーはリストを検索して、シーフードグループ化された製品の開始場所と終了場所を探す必要があります。 結果はカテゴリ名によってアルファベット順に並べられているため、シーフード製品を見つけることは難しくありませんが、グリッド内の項目のリストを厳密にスキャンする必要があります。
並べ替えられたグループ間の境界を強調するために、多くの Web サイトでは、このようなグループ間に区切り記号を追加するユーザー インターフェイスが採用されています。 図 1 に示すような区切り記号を使用すると、ユーザーは特定のグループをより迅速に見つけて境界を識別し、データに存在する個別のグループを確認できます。
図 1: 各カテゴリ グループが明確に識別されている (フルサイズの画像を表示する をクリックします)
このチュートリアルでは、このような並べ替えユーザー インターフェイスを作成する方法について説明します。
手順 1: 標準の並べ替え可能な GridView を作成する
強化された並べ替えインターフェイスを提供するために GridView を拡張する方法を調べる前に、まず、製品を一覧表示する標準の並べ替え可能な GridView を作成しましょう。 まず、CustomSortingUI.aspx
フォルダーの PagingAndSorting
ページを開きます。 ページに GridView を追加し、その ID
プロパティを ProductList
に設定し、それを新しい ObjectDataSource にバインドします。 レコードを選択するために ProductsBLL
クラスの GetProducts()
メソッドを使用するように ObjectDataSource を構成します。
次に、BoundFields と Discontinued CheckBoxField の ProductName
、 CategoryName
、 SupplierName
、 UnitPrice
のみを含む GridView を構成します。 最後に、GridView のスマート タグの [並べ替えを有効にする] チェック ボックスをオンにするか、その AllowSorting
プロパティを true
に設定して、並べ替えをサポートするように GridView を構成します。
CustomSortingUI.aspx
ページにこれらの追加を行った後、宣言型マークアップは次のようになります。
<asp:GridView ID="ProductList" runat="server" AllowSorting="True"
AutoGenerateColumns="False" DataKeyNames="ProductID"
DataSourceID="ObjectDataSource1" EnableViewState="False">
<Columns>
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
ReadOnly="True" SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier"
ReadOnly="True" SortExpression="SupplierName" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}"
HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
TypeName="ProductsBLL"></asp:ObjectDataSource>
ブラウザーでこれまでの進行状況を確認してください。 図 2 は、データがカテゴリ別にアルファベット順に並べ替えられている場合の並べ替え可能な GridView を示しています。
図 2: 並べ替え可能な GridView のデータがカテゴリ別に並べ替えられています (フルサイズの画像を表示する をクリックします)。
手順 2: 区切り文字行を追加する手法の探索
汎用の並べ替え可能な GridView が完了したら、残っているのは、一意の並べ替えられた各グループの前に GridView に区切り行を追加できることです。 しかし、このような行を GridView に挿入するにはどうすればよいですか? 基本的に、GridView の行を反復処理し、並べ替えられた列の値の違いがどこで発生するかを判断し、適切な区切り行を追加する必要があります。 この問題について考えると、ソリューションが GridView の RowDataBound
イベント ハンドラー内のどこかにあるのは当然のようです。 「 データに基づくカスタム書式設定」 チュートリアルで説明したように、このイベント ハンドラーは、行のデータに基づいて行レベルの書式設定を適用するときによく使用されます。 ただし、 RowDataBound
イベント ハンドラーは、このイベント ハンドラーからプログラムで GridView に行を追加できないため、ここではソリューションではありません。 GridView の Rows
コレクションは、実際には読み取り専用です。
GridView に行を追加するには、次の 3 つの選択肢があります。
- これらのメタデータ区切り文字行を、GridView にバインドされている実際のデータに追加します。
- GridView がデータにバインドされたら、GridView のコントロール コレクションに
TableRow
インスタンスを追加します - GridView コントロールを拡張し、GridView の構造の構築を担当するメソッドをオーバーライドするカスタム サーバー コントロールを作成する
多くの Web ページまたは複数の Web サイトでこの機能が必要な場合は、カスタム サーバー コントロールを作成することをお勧めします。 ただし、これにはかなりのコードと、GridView の内部作業の深さへの徹底的な調査が必要になります。 そのため、このチュートリアルではこのオプションは考慮しません。
他の 2 つのオプションでは、GridView にバインドされている実際のデータに区切り線を追加し、バインドされた後に GridView のコントロール コレクションを操作します。問題を異なる方法で攻撃し、議論にメリットがあります。
GridView にバインドされたデータに行を追加する
GridView がデータ ソースにバインドされると、データ ソースによって返されるレコードごとに GridViewRow
が作成されます。 そのため、GridView にバインドする前にデータ ソースに区切り記号レコードを追加することで、必要な区切り文字行を挿入できます。 図 3 は、この概念を示しています。
図 3: データ ソースに区切り文字行を追加する 1 つの手法
特別な区切り記号レコードがないため、区切り記号レコードという用語を引用符で囲んで使用します。代わりに、データ ソース内の特定のレコードが通常のデータ行ではなく区切り記号として機能するようにフラグを設定する必要があります。 この例では、ProductsDataTable
で構成される GridView にProductRows
インスタンスをバインドしています。 レコードの CategoryID
プロパティを -1
に設定することで、レコードに区切り行としてフラグを設定する場合があります (このような値は正常に存在できなかったため)。
この手法を利用するには、次の手順を実行する必要があります。
- GridView (
ProductsDataTable
インスタンス) にバインドするデータをプログラムで取得する - GridView の
SortExpression
プロパティとSortDirection
プロパティに基づいてデータを並べ替える -
ProductsRows
内のProductsDataTable
を反復処理して、並べ替えられた列の違いがどこにあるかを調べる - 各グループ境界では、
ProductsRow
のインスタンスを DataTable に区切り記号として挿入してください。このレコードはCategoryID
に-1
(または区切り記号レコードとしてマークするために決定された指定) が設定されています。 - 区切り文字行を挿入した後、プログラムによってデータを GridView にバインドします。
これら 5 つの手順に加えて、GridView の RowDataBound
イベントのイベント ハンドラーも提供する必要があります。 ここでは、各 DataRow
を確認し、CategoryID
設定が -1
である区切り行だったかどうかを判断します。 その場合は、書式設定やセルに表示されるテキストを調整する必要があります。
並べ替えグループの境界を挿入するこの手法を使用するには、GridView の Sorting
イベントのイベント ハンドラーを提供し、 SortExpression
と SortDirection
の値を追跡する必要もあるため、上記よりも少し多くの作業が必要です。
データバインドされた後の GridView のコントロール コレクションの操作
データを GridView にバインドする前にメッセージングするのではなく、データが GridView にバインドされた 後 に区切り線の行を追加できます。 データ バインディングのプロセスによって GridView のコントロール階層が構築されます。実際には、単に行のコレクションで構成される Table
インスタンスであり、それぞれがセルのコレクションで構成されます。 具体的には、GridView のコントロール コレクションには、そのルートにあるTable
オブジェクト、GridView にバインドされたGridViewRow
内の各レコードのTableRow
(DataSource
クラスから派生した) と、TableCell
の各データ フィールドの各GridViewRow
インスタンス内のDataSource
オブジェクトが含まれています。
各並べ替えグループの間に区切り文字行を追加するには、作成後にこのコントロール階層を直接操作します。 ページがレンダリングされるまでに、GridView のコントロール階層が最後に作成されたと確信できます。 したがって、この方法では、 Page
クラスの Render
メソッドがオーバーライドされます。その時点で、GridView の最終的なコントロール階層が更新され、必要な区切り文字行が含まれます。 図 4 は、このプロセスを示しています。
図 4: GridView のコントロール階層を操作する代替手法 (フルサイズの画像を表示する をクリックします)。
このチュートリアルでは、この後者の方法を使用して、並べ替えのユーザー エクスペリエンスをカスタマイズします。
注
このチュートリアルで紹介するコードは、Teemu Keiskiのブログエントリ、「GridView Sort Groupingで少し遊ぶ」で提供されている例に基づいています。
手順 3: GridView のコントロール階層に区切り線行を追加する
コントロール階層が作成され、そのページの最後の訪問時に作成された後にのみ、区切り行を GridView コントロール階層に追加する必要があるため、この追加はページ ライフサイクルの最後に実行します。ただし、実際の GridView コントロール階層が HTML にレンダリングされる前に行います。 これを実現できる最新のポイントは、次のメソッド シグネチャを使用して分離コード クラスでオーバーライドできる Page
クラスの Render
イベントです。
protected override void Render(HtmlTextWriter writer)
{
// Add code to manipulate the GridView control hierarchy
base.Render(writer);
}
Page
クラスの元のRender
メソッドが呼び出されると、ページ内の各コントロールがレンダリングbase.Render(writer)
、コントロール階層に基づいてマークアップが生成されます。 したがって、両方とも base.Render(writer)
を呼び出してページがレンダリングされるようにし、 base.Render(writer)
を呼び出す前に GridView のコントロール階層を操作し、描画前に区切り線行が GridView のコントロール階層に追加されるようにすることが不可欠です。
並べ替えグループ ヘッダーを挿入するには、まず、ユーザーがデータの並べ替えを要求したことを確認する必要があります。 既定では、GridView の内容は並べ替えされないため、グループの並べ替えヘッダーを入力する必要はありません。
注
ページが最初に読み込まれるときに GridView を特定の列で並べ替えるには、最初のページアクセス時に GridView の Sort
メソッドを呼び出します (後続のポストバックでは呼び出しません)。 これを実現するには、Page_Load
条件内のif (!Page.IsPostBack)
イベント ハンドラーにこの呼び出しを追加します。
メソッドの詳細については、Sort
関するチュートリアルの情報を参照してください。
データが並べ替えられたと仮定して、次のタスクは、データが並べ替えられた列を特定し、その列の値の違いを探して行をスキャンすることです。 次のコードは、データが並べ替えられていることを確認し、データが並べ替えられた列を検索します。
protected override void Render(HtmlTextWriter writer)
{
// Only add the sorting UI if the GridView is sorted
if (!string.IsNullOrEmpty(ProductList.SortExpression))
{
// Determine the index and HeaderText of the column that
//the data is sorted by
int sortColumnIndex = -1;
string sortColumnHeaderText = string.Empty;
for (int i = 0; i < ProductList.Columns.Count; i++)
{
if (ProductList.Columns[i].SortExpression.CompareTo(ProductList.SortExpression)
== 0)
{
sortColumnIndex = i;
sortColumnHeaderText = ProductList.Columns[i].HeaderText;
break;
}
}
// TODO: Scan the rows for differences in the sorted column�s values
}
GridView がまだ並べ替えられていない場合、GridView の SortExpression
プロパティは設定されていません。 したがって、このプロパティに何らかの値がある場合にのみ、区切り文字の行を追加します。 その場合は、次に、データが並べ替えられた列のインデックスを決定する必要があります。 これを行うには、GridView の Columns
コレクションをループ処理し、 SortExpression
プロパティが GridView の SortExpression
プロパティと等しい列を検索します。 列のインデックスに加えて、区切り記号の行を表示するときに使用される HeaderText
プロパティも取得します。
データの並べ替えに使用する列のインデックスを使用して、最後の手順では GridView の行を列挙します。 各行について、並べ替えられた列の値が前の行の並べ替えられた列の値と異なるかどうかを判断する必要があります。 その場合は、新しい GridViewRow
インスタンスをコントロール階層に挿入する必要があります。 これを行うには、次のコードを使用します。
protected override void Render(HtmlTextWriter writer)
{
// Only add the sorting UI if the GridView is sorted
if (!string.IsNullOrEmpty(ProductList.SortExpression))
{
// ... Code for finding the sorted column index removed for brevity ...
// Reference the Table the GridView has been rendered into
Table gridTable = (Table)ProductList.Controls[0];
// Enumerate each TableRow, adding a sorting UI header if
// the sorted value has changed
string lastValue = string.Empty;
foreach (GridViewRow gvr in ProductList.Rows)
{
string currentValue = gvr.Cells[sortColumnIndex].Text;
if (lastValue.CompareTo(currentValue) != 0)
{
// there's been a change in value in the sorted column
int rowIndex = gridTable.Rows.GetRowIndex(gvr);
// Add a new sort header row
GridViewRow sortRow = new GridViewRow(rowIndex, rowIndex,
DataControlRowType.DataRow, DataControlRowState.Normal);
TableCell sortCell = new TableCell();
sortCell.ColumnSpan = ProductList.Columns.Count;
sortCell.Text = string.Format("{0}: {1}",
sortColumnHeaderText, currentValue);
sortCell.CssClass = "SortHeaderRowStyle";
// Add sortCell to sortRow, and sortRow to gridTable
sortRow.Cells.Add(sortCell);
gridTable.Controls.AddAt(rowIndex, sortRow);
// Update lastValue
lastValue = currentValue;
}
}
}
base.Render(writer);
}
このコードは、まず、GridView のコントロール階層のルートにある Table
オブジェクトをプログラムで参照し、 lastValue
という名前の文字列変数を作成します。
lastValue
は、現在の行の並べ替えられた列の値と前の行の値を比較するために使用されます。 次に、GridView の Rows
コレクションが列挙され、各行に対して、並べ替えられた列の値が currentValue
変数に格納されます。
注
特定の行の並べ替えられた列の値を決定するには、セルの Text
プロパティを使用します。 これは BoundFields には適していますが、TemplateFields、CheckBoxFields などでは必要に応じて機能しません。 ここでは、間もなく別の GridView フィールドを考慮する方法について説明します。
その後、 currentValue
変数と lastValue
変数が比較されます。 異なる場合は、新しい区切り記号行をコントロール階層に追加する必要があります。 これを行うには、GridViewRow
オブジェクトのTable
コレクション内のRows
のインデックスを決定し、新しいGridViewRow
インスタンスとTableCell
インスタンスを作成してから、TableCell
とGridViewRow
をコントロール階層に追加します。
区切り行の 1 行 TableCell
は、GridView の幅全体にまたがるように書式設定され、 SortHeaderRowStyle
CSS クラスを使用して書式設定され、並べ替えグループ名 (Category など) とグループの値 (飲料など) の両方を表示するように Text
プロパティを持ちます。 最後に、 lastValue
が currentValue
の値に更新されます。
並べ替えグループ ヘッダー行の書式設定に使用する CSS クラス SortHeaderRowStyle
、 Styles.css
ファイルで指定する必要があります。 お好みのスタイル設定を自由にお使いください。私は次の設定を使用しました。
.SortHeaderRowStyle
{
background-color: #c00;
text-align: left;
font-weight: bold;
color: White;
}
現在のコードでは、並べ替えインターフェイスは、BoundField による並べ替え時に並べ替えグループ ヘッダーを追加します (図 5 を参照してください。これは、サプライヤーによる並べ替え時のスクリーンショットを示しています)。 ただし、他のフィールド型 (CheckBoxField や TemplateField など) で並べ替える場合、並べ替えグループヘッダーはどこにも見つかりません (図 6 を参照)。
図 5: 並べ替えインターフェイスには、BoundFields による並べ替え時に並べ替えグループ ヘッダーが含まれています (フルサイズの画像を表示する をクリックします)。
図 6: CheckBoxField の並べ替え時にグループ ヘッダーの並べ替えが見つかりません (フルサイズの画像を表示する をクリックします)。
CheckBoxField による並べ替え時に並べ替えグループ ヘッダーが見つからない理由は、現在、コードで TableCell
の Text
プロパティのみを使用して各行の並べ替えられた列の値を決定するためです。 CheckBoxFields の場合、 TableCell
の Text
プロパティは空の文字列です。代わりに、 TableCell
の Controls
コレクション内にある CheckBox Web コントロールを使用して値を使用できます。
BoundFields 以外のフィールド型を処理するには、 currentValue
変数が割り当てられているコードを拡張して、 TableCell
の Controls
コレクションに CheckBox が存在するかどうかを確認する必要があります。
currentValue = gvr.Cells[sortColumnIndex].Text
を使用する代わりに、このコードを次のように置き換えます。
string currentValue = string.Empty;
if (gvr.Cells[sortColumnIndex].Controls.Count > 0)
{
if (gvr.Cells[sortColumnIndex].Controls[0] is CheckBox)
{
if (((CheckBox)gvr.Cells[sortColumnIndex].Controls[0]).Checked)
currentValue = "Yes";
else
currentValue = "No";
}
// ... Add other checks here if using columns with other
// Web controls in them (Calendars, DropDownLists, etc.) ...
}
else
currentValue = gvr.Cells[sortColumnIndex].Text;
このコードでは、現在の行の並べ替えられた列 TableCell
を調べて、 Controls
コレクションにコントロールがあるかどうかを判断します。 1 つ目のコントロールが CheckBox の場合、CheckBox のcurrentValue
プロパティに応じて、Checked
変数は Yes または No に設定されます。 それ以外の場合、値は TableCell
の Text
プロパティから取得されます。 このロジックをレプリケートして、GridView に存在する可能性がある TemplateField の並べ替えを処理できます。
上記のコードの追加により、中止された CheckBoxField による並べ替え時に並べ替えグループ ヘッダーが存在するようになりました (図 7 を参照)。
図 7: CheckBoxField の並べ替え時にグループ ヘッダーの並べ替えが表示されるようになりました (フルサイズの画像を表示する をクリックします)。
注
NULL
、CategoryID
、またはSupplierID
フィールドにUnitPrice
データベース値を持つ製品がある場合、これらの値は既定でGridViewに空の文字列として表示されます。つまり、NULL
値を持つ製品の区切り行のテキストは「Category:」のように読み上げられます(つまり、「Category: Beverages」のように、「Category:」の後に名前が表示されないことを意味します)。 ここに値を表示する場合は、表示するテキストに BoundFields NullDisplayText
プロパティ を設定するか、区切り行のプロパティに currentValue
を割り当てるときに Render メソッドに条件付きステートメント Text
追加できます。
概要
GridView には、並べ替えインターフェイスをカスタマイズするための組み込みオプションが多数含まれていません。 ただし、少し低レベルのコードでは、GridView のコントロール階層を調整して、よりカスタマイズされたインターフェイスを作成できます。 このチュートリアルでは、並べ替え可能な GridView の並べ替えグループ区切り行を追加する方法について説明しました。これにより、個別のグループとグループの境界をより簡単に識別できます。 カスタマイズされた並べ替えインターフェイスのその他の例については、 Scott Guthrie s A Few ASP.NET 2.0 GridView の並べ替えのヒントとテクニック に関するブログ エントリを参照してください。
プログラミングに満足!
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを使用しています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・セルフ ASP.NET 24時間で2.0です。 彼には mitchell@4GuysFromRolla.comで連絡できます。