カスタマイズされた並べ替えユーザー インターフェイスを作成する (VB)
並べ替えられたデータの長いリストを表示する場合は、区切り行を導入して関連するデータをグループ化すると非常に役立ちます。 このチュートリアルでは、このような並べ替えユーザー インターフェイスを作成する方法について説明します。
はじめに
並べ替えられた列に少数の異なる値しかない並べ替えられたデータの長いリストを表示する場合、違いの境界が発生する場所を、エンド ユーザーが正確に識別するのが難しい場合があります。 たとえば、データベースには 81 個の製品がありますが、カテゴリの選択肢は 9 つだけです (8 つの一意のカテゴリと NULL
オプション)。 シーフード カテゴリに該当する製品の調査に関心があるユーザーの場合を考えてみましょう。 単一の GridView にすべての製品がリストされているページから、ユーザーは、結果をカテゴリ別に並べ替え、すべてのシーフード製品がグループ化されることが最善の策であると判断する場合があります。 カテゴリ別に並べ替えた後、ユーザーはリストを検索して、シーフードのグループに含まれる製品の開始場所と終了場所を探す必要があります。 結果はカテゴリ名がアルファベット順に並べられているため、シーフード製品を見つけることは難しくありませんが、グリッド内の項目のリストを厳密にスキャンする必要があります。
並べ替えられたグループ間の境界を強調するために、多くの Web サイトでは、このようなグループ間に区切りを追加するユーザー インターフェイスが採用されています。 図 1 に示すような区切りを使用すると、ユーザーは特定のグループをより迅速に見つけて境界を識別し、データに存在する個別のグループを確認できます。
図 1: 各カテゴリ グループが明確に識別されている (クリックするとフルサイズの画像が表示されます)
このチュートリアルでは、このような並べ替えユーザー インターフェイスを作成する方法について説明します。
手順 1: 標準の並べ替え可能な GridView を作成する
強化された並べ替えインターフェイスを提供するために GridView を拡張する方法を調べる前に、まず、製品を一覧表示する標準の並べ替え可能な GridView を作成しましょう。 まず、PagingAndSorting
フォルダーの CustomSortingUI.aspx
ページを開きます。 ページに GridView を追加し、その ID
プロパティを ProductList
に設定し、それを新しい ObjectDataSource にバインドします。 レコードを選択するために ProductsBLL
クラスの GetProducts()
メソッドを使用するように ObjectDataSource を構成します。
次に、ProductName
、CategoryName
、SupplierName
、UnitPrice
BoundField と Discontinued CheckBoxField のみが含まれる 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
インスタンスを、ProductRows
で構成される GridView にバインドしています。 CategoryID
プロパティを -1
に設定すると、レコードに区切り行としてのフラグを設定できる場合があります (このような値は通常は存在しないため)。
この手法を利用するには、次の手順を実行する必要があります。
- GridView (
ProductsDataTable
インスタンス) にバインドするデータをプログラムで取得します - GridView の
SortExpression
とSortDirection
プロパティに基づいてデータを並べ替えます ProductsDataTable
内のProductsRows
を反復処理して、並べ替えられた列の違いがどこにあるかを調べます- 各グループの境界で、
ProductsRow
インスタンス区切りレコードを DataTable に挿入します。そのCategoryID
があるものが-1
(または、レコードを区切りレコードとしてマークすることに決定された任意の名称) に設定されています - 区切り行を挿入した後、データを GridView にプログラムによってバインドします
これら 5 つの手順に加えて、GridView の RowDataBound
イベントのイベント ハンドラーも提供する必要があります。 ここでは、各 DataRow
を確認し、CategoryID
設定が -1
の場合は、区切り行であるかどうかを判断します。 その場合は、書式設定やセルに表示されるテキストを調整する必要がある場合があります。
並べ替えグループの境界を挿入するためにこの手法を使用するには、GridView の Sorting
イベントのイベント ハンドラーを提供し、SortExpression
と SortDirection
値を追跡する必要もあるため、上記で概説したよりも少し多い作業が必要です。
データ バインドされた後の GridView のコントロール コレクションの操作
データを GridView にバインドする前にメッセージングするのではなく、データが GridView にバインドされた後に区切り行を追加できます。 データ バインディングのプロセスによって GridView のコントロール階層が構築されます。実際には、単に、それぞれがセルのコレクションで構成されている行のコレクションから成る Table
インスタンスです。 具体的には、GridView のコントロール コレクションには、そのルートに Table
オブジェクト、GridView にバインドされた DataSource
内の各レコードの GridViewRow
(TableRow
クラスから派生) と、DataSource
の各データ フィールドの各 GridViewRow
インスタンス内の TableCell
オブジェクトが含まれています。
各並べ替えグループの間に区切り行を追加するには、このコントロール階層の作成後にこれを直接操作します。 ページがレンダリングされるまでに、GridView のコントロール階層が最後に作成されたことがわかります。 したがって、この方法では、Page
クラスの Render
メソッドがオーバーライドされます。その時点で、GridView の最終的なコントロール階層が、必要な区切り行を含むように更新されます。 このプロセスを図 4 に示します。
図 4: GridView のコントロール階層を操作する代替手法 (クリックするとフルサイズの画像が表示されます)
このチュートリアルでは、この後者の方法を使用して、並べ替えのユーザー エクスペリエンスをカスタマイズします。
Note
このチュートリアルで紹介するコードは、Teemu Keiski のブログ エントリ「GridView の並べ替えグループで少し遊ぶ」で提供されている例に基づいています。
手順 3: GridView のコントロール階層に区切り行を追加する
コントロール階層が作成され、そのページの最後の訪問時に作成された後にのみ、区切り行を GridView コントロール階層に追加する必要があるため、この追加はページのライフサイクルの最後、ただし、実際の GridView コントロール階層が HTML にレンダリングされる前に行います。 これを実現できる最新のポイントは、次のメソッド シグネチャを使用して分離コード クラスでオーバーライドできる Page
クラスの Render
イベントです。
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
' Add code to manipulate the GridView control hierarchy
MyBase.Render(writer)
End Sub
Page
クラスの元の Render
メソッドが base.Render(writer)
で呼び出されると、ページ内の各コントロールがレンダリングされ、コントロール階層に基づいてマークアップが生成されます。 したがって、ページがレンダリングされるように base.Render(writer)
を呼び出すことと、GridView のコントロール階層がレンダリングされる前に、区切り行が追加されるように、base.Render(writer)
を呼び出す前に GridView のコントロール階層を操作することが不可欠です。
並べ替えグループ ヘッダーを挿入するには、まず、ユーザーがデータの並べ替えを要求したことを確認する必要があります。 既定では、GridView の内容は並べ替えされないため、グループの並べ替えヘッダーを入力する必要はありません。
Note
ページが最初に読み込まれるときに GridView を特定の列で並べ替えたい場合は、ページに最初にアクセスする時に GridView の Sort
メソッドを呼び出します (後続のポストバックでは行いません)。 これを実現するには、if (!Page.IsPostBack)
条件内の Page_Load
イベント ハンドラーにこの呼び出しを追加します。 Sort
メソッドの詳細については、「レポート データのページングと並べ替え」チュートリアルの情報を参照してください。
データが並べ替えられたと仮定して、次のタスクは、データが並べ替えられた列を特定し、その列の値の違いを探して行をスキャンすることです。 次のコードは、データが並べ替えられていることを確認し、データが並べ替えられた列を検索します。
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
' Only add the sorting UI if the GridView is sorted
If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
' Determine the index and HeaderText of the column that
'the data is sorted by
Dim sortColumnIndex As Integer = -1
Dim sortColumnHeaderText As String = String.Empty
For i As Integer = 0 To ProductList.Columns.Count - 1
If ProductList.Columns(i).SortExpression.CompareTo( _
ProductList.SortExpression) = 0 Then
sortColumnIndex = i
sortColumnHeaderText = ProductList.Columns(i).HeaderText
Exit For
End If
Next
' TODO: Scan the rows for differences in the sorted column�s values
End Sub
GridView がまだ並べ替えられていない場合、GridView の SortExpression
プロパティは設定されません。 したがって、このプロパティに何らかの値がある場合にのみ、区切り行を追加します。 その場合は、データが並べ替えられた列のインデックスを決定する必要があります。 これを行うには、GridView の Columns
コレクションをループ処理し、SortExpression
プロパティが GridView の SortExpression
プロパティと等しい列を検索します。 列のインデックスに加えて、HeaderText
プロパティも取得します。これは、区切り行を表示するときに使用されます。
データの並べ替えに使用する列のインデックスを使用して、最後の手順では GridView の行を列挙します。 各行について、並べ替えられた列の値が前の行の並べ替えられた列の値と異なるかどうかを判断する必要があります。 該当する場合は、新しい GridViewRow
インスタンスをコントロール階層に挿入する必要があります。 これを行うには、次のコードを使用します。
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
' Only add the sorting UI if the GridView is sorted
If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
' ... Code for finding the sorted column index removed for brevity ...
' Reference the Table the GridView has been rendered into
Dim gridTable As Table = CType(ProductList.Controls(0), Table)
' Enumerate each TableRow, adding a sorting UI header if
' the sorted value has changed
Dim lastValue As String = String.Empty
For Each gvr As GridViewRow In ProductList.Rows
Dim currentValue As String = gvr.Cells(sortColumnIndex).Text
If lastValue.CompareTo(currentValue) <> 0 Then
' there's been a change in value in the sorted column
Dim rowIndex As Integer = gridTable.Rows.GetRowIndex(gvr)
' Add a new sort header row
Dim sortRow As New GridViewRow(rowIndex, rowIndex, _
DataControlRowType.DataRow, DataControlRowState.Normal)
Dim sortCell As 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
End If
Next
End If
MyBase.Render(writer)
End Sub
このコードは、まず、GridView のコントロール階層のルートにある Table
オブジェクトをプログラムを使って参照し、lastValue
という名前の文字列変数を作成します。 lastValue
は、現在の行の並べ替えられた列の値と前の行の値の比較に使用されます。 次に、GridView の Rows
コレクションが列挙され、各行に対して、並べ替えられた列の値が currentValue
変数に格納されます。
Note
特定の行の並べ替えられた列の値を決定するには、セルの Text
プロパティを使用します。 これは BoundField には適していますが、TemplateField、CheckBoxField などでは期待どおりに機能しません。 間もなく、別の GridView フィールドを考慮する方法について説明します。
次に currentValue
と lastValue
変数が比較されます。 これらが異なる場合は、新しい区切り行をコントロール階層に追加する必要があります。 これを行うには、Table
オブジェクトの Rows
コレクション内の GridViewRow
のインデックスを決定し、新しい GridViewRow
および TableCell
インスタンスを作成してから、コントロール階層に TableCell
と GridViewRow
を追加します。
区切り行の唯一の TableCell
は、SortHeaderRowStyle
CSS クラスを使用して書式設定される GridView の幅全体にまたがるように書式設定され、並べ替えグループ名 (Category など) とグループの値 (Beverages など) の両方を表示する Text
プロパティを持つことに注意してください。 最後に、lastValue
が currentValue
の値に更新されます。
並べ替えグループ ヘッダー行 SortHeaderRowStyle
の書式設定に使用する CSS クラスは、Styles.css
ファイルで指定する必要があります。 お好きなスタイル設定を自由に使用してください。私は次を使用しました:
.SortHeaderRowStyle
{
background-color: #c00;
text-align: left;
font-weight: bold;
color: White;
}
現在のコードでは、並べ替えインターフェイスは、任意の BoundField による並べ替え時に並べ替えグループ ヘッダーを追加します (図 5 を参照してください。これは、サプライヤーで並べ替えた場合のスクリーンショットです)。 ただし、他のフィールド型 (CheckBoxField や TemplateField など) で並べ替える場合、並べ替えグループ ヘッダーはどこにも見つかりません (図 6 を参照してください)。
図 5: 並べ替えインターフェイスには、BoundField による並べ替え時に並べ替えグループ ヘッダーが含まれます (クリックするとフルサイズの画像が表示されます)
図 6: CheckBoxField の並べ替え時は並べ替えグループ ヘッダーがありません (クリックするとフルサイズの画像が表示されます)
CheckBoxField による並べ替え時に並べ替えグループ ヘッダーが見つからない理由は、コードで現在、各行の並べ替えられた列の値を決定するために TableCell
の Text
プロパティのみが使用されているためです。 CheckBoxField の場合、TableCell
の Text
プロパティは空の文字列です。代わりに、値は、TableCell
の Controls
コレクション内にある CheckBox Web コントロールを介して使用できます。
BoundField 以外のフィールド型を処理するには、TableCell
の Controls
コレクションに CheckBox が存在することを確認するために、currentValue
変数がチェックに割り当てられているコードを拡張する必要があります。 currentValue = gvr.Cells(sortColumnIndex).Text
を使用する代わりに、このコードを次のものに置き換えます。
Dim currentValue As String = String.Empty
If gvr.Cells(sortColumnIndex).Controls.Count > 0 Then
If TypeOf gvr.Cells(sortColumnIndex).Controls(0) Is CheckBox Then
If CType(gvr.Cells(sortColumnIndex).Controls(0), CheckBox).Checked Then
currentValue = "Yes"
Else
currentValue = "No"
End If
' ... Add other checks here if using columns with other
' Web controls in them (Calendars, DropDownLists, etc.) ...
End If
Else
currentValue = gvr.Cells(sortColumnIndex).Text
End If
このコードでは、現在の行の並べ替えられた列 TableCell
を調べて、Controls
コレクションにコントロールがあるかどうかを判断します。 存在する場合で、1 つ目のコントロールが CheckBox の場合、CheckBox の Checked
プロパティに応じて、currentValue
変数は Yes または No に設定されます。 それ以外の場合、値は TableCell
の Text
プロパティから取得されます。 このロジックを複製して、GridView に存在する可能性があるすべての TemplateField の並べ替えを処理できます。
上記のコードの追加により、Discontinued CheckBoxField による並べ替え時に並べ替えグループ ヘッダーが存在するようになりました (図 7 を参照)。
図 7: CheckBoxField の並べ替え時にグループ ヘッダーの並べ替えが存在するようになりました (クリックするとフルサイズの画像が表示されます)
Note
CategoryID
、SupplierID
、または UnitPrice
フィールドが NULL
データベース値の製品がある場合、これらの値は、既定では GridView に空の文字列として表示されます。つまり、NULL
値を持つ製品の区切り行のテキストは、Category: のように表示されます (Category: Beverages のような名前が Category: の後にありません)。 ここに値を表示したい場合は、BoundField NullDisplayText
プロパティを表示したいテキストに設定するか、currentValue
を区切り行の Text
プロパティに割り当てるときに Render メソッドに条件付きステートメントを追加できます。
まとめ
GridView には、並べ替えインターフェイスをカスタマイズするための組み込みオプションがあまり含まれていません。 ただし、少し低レベルのコードでは、GridView のコントロール階層を微調整して、よりカスタマイズされたインターフェイスを作成できます。 このチュートリアルでは、並べ替え可能な GridView の並べ替えグループ区切り行を追加する方法について説明しました。これにより、個別のグループとグループの境界をより簡単に識別できます。 カスタマイズされた並べ替えインターフェイスのその他の例については、Scott Guthrie の 「ASP.NET 2.0 GridView の並べ替えのヒントとコツ」のブログ エントリをご覧ください。
プログラミングに満足!
著者について
Scott Mitchell は、7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者です。1998 年から Microsoft の Web テクノロジに携わっています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。