Share via


カスタマイズされた並べ替えユーザー インターフェイスを作成する (VB)

作成者: Scott Mitchell

PDF のダウンロード

並べ替えられたデータの長いリストを表示する場合は、区切り行を導入して関連するデータをグループ化すると非常に役立ちます。 このチュートリアルでは、このような並べ替えユーザー インターフェイスを作成する方法について説明します。

はじめに

並べ替えられた列に少数の異なる値しかない並べ替えられたデータの長いリストを表示する場合、違いの境界が発生する場所を、エンド ユーザーが正確に識別するのが難しい場合があります。 たとえば、データベースには 81 個の製品がありますが、カテゴリの選択肢は 9 つだけです (8 つの一意のカテゴリと NULL オプション)。 シーフード カテゴリに該当する製品の調査に関心があるユーザーの場合を考えてみましょう。 単一の GridView にすべての製品がリストされているページから、ユーザーは、結果をカテゴリ別に並べ替え、すべてのシーフード製品がグループ化されることが最善の策であると判断する場合があります。 カテゴリ別に並べ替えた後、ユーザーはリストを検索して、シーフードのグループに含まれる製品の開始場所と終了場所を探す必要があります。 結果はカテゴリ名がアルファベット順に並べられているため、シーフード製品を見つけることは難しくありませんが、グリッド内の項目のリストを厳密にスキャンする必要があります。

並べ替えられたグループ間の境界を強調するために、多くの Web サイトでは、このようなグループ間に区切りを追加するユーザー インターフェイスが採用されています。 図 1 に示すような区切りを使用すると、ユーザーは特定のグループをより迅速に見つけて境界を識別し、データに存在する個別のグループを確認できます。

Each Category Group is Clearly Identified

図 1: 各カテゴリ グループが明確に識別されている (クリックするとフルサイズの画像が表示されます)

このチュートリアルでは、このような並べ替えユーザー インターフェイスを作成する方法について説明します。

手順 1: 標準の並べ替え可能な GridView を作成する

強化された並べ替えインターフェイスを提供するために GridView を拡張する方法を調べる前に、まず、製品を一覧表示する標準の並べ替え可能な GridView を作成しましょう。 まず、PagingAndSorting フォルダーの CustomSortingUI.aspx ページを開きます。 ページに GridView を追加し、その ID プロパティを ProductList に設定し、それを新しい ObjectDataSource にバインドします。 レコードを選択するために ProductsBLL クラスの GetProducts() メソッドを使用するように ObjectDataSource を構成します。

次に、ProductNameCategoryNameSupplierNameUnitPrice 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 を示しています。

The Sortable GridView s Data is Ordered by Category

図 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 は、この概念を示しています。

One Technique Involves Adding Separator Rows to the Data Source

図 3: データ ソースに区切り行を追加する 1 つの手法

特別な区切り記号レコードがないため、区切り記号レコードという用語を引用符で囲んで使用します。むしろ、データ ソース内の特定のレコードが通常のデータ行ではなく区切り記号として機能するようにフラグを設定する必要があります。 この例では、ProductsDataTable インスタンスを、ProductRows で構成される GridView にバインドしています。 CategoryID プロパティを -1 に設定すると、レコードに区切り行としてのフラグを設定できる場合があります (このような値は通常は存在しないため)。

この手法を利用するには、次の手順を実行する必要があります。

  1. GridView ( ProductsDataTable インスタンス) にバインドするデータをプログラムで取得します
  2. GridView の SortExpressionSortDirection プロパティに基づいてデータを並べ替えます
  3. ProductsDataTable 内の ProductsRows を反復処理して、並べ替えられた列の違いがどこにあるかを調べます
  4. 各グループの境界で、ProductsRow インスタンス区切りレコードを DataTable に挿入します。その CategoryID があるものが -1 (または、レコードを区切りレコードとしてマークすることに決定された任意の名称) に設定されています
  5. 区切り行を挿入した後、データを GridView にプログラムによってバインドします

これら 5 つの手順に加えて、GridView の RowDataBound イベントのイベント ハンドラーも提供する必要があります。 ここでは、各 DataRow を確認し、CategoryID 設定が -1 の場合は、区切り行であるかどうかを判断します。 その場合は、書式設定やセルに表示されるテキストを調整する必要がある場合があります。

並べ替えグループの境界を挿入するためにこの手法を使用するには、GridView の Sorting イベントのイベント ハンドラーを提供し、SortExpressionSortDirection 値を追跡する必要もあるため、上記で概説したよりも少し多い作業が必要です。

データ バインドされた後の GridView のコントロール コレクションの操作

データを GridView にバインドする前にメッセージングするのではなく、データが GridView にバインドされた後に区切り行を追加できます。 データ バインディングのプロセスによって GridView のコントロール階層が構築されます。実際には、単に、それぞれがセルのコレクションで構成されている行のコレクションから成る Table インスタンスです。 具体的には、GridView のコントロール コレクションには、そのルートに Table オブジェクト、GridView にバインドされた DataSource 内の各レコードの GridViewRow (TableRow クラスから派生) と、DataSource の各データ フィールドの各 GridViewRow インスタンス内の TableCell オブジェクトが含まれています。

各並べ替えグループの間に区切り行を追加するには、このコントロール階層の作成後にこれを直接操作します。 ページがレンダリングされるまでに、GridView のコントロール階層が最後に作成されたことがわかります。 したがって、この方法では、Page クラスの Render メソッドがオーバーライドされます。その時点で、GridView の最終的なコントロール階層が、必要な区切り行を含むように更新されます。 このプロセスを図 4 に示します。

An Alternate Technique Manipulates the GridView s Control Hierarchy

図 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 フィールドを考慮する方法について説明します。

次に currentValuelastValue 変数が比較されます。 これらが異なる場合は、新しい区切り行をコントロール階層に追加する必要があります。 これを行うには、Table オブジェクトの Rows コレクション内の GridViewRow のインデックスを決定し、新しい GridViewRow および TableCell インスタンスを作成してから、コントロール階層に TableCellGridViewRow を追加します。

区切り行の唯一の TableCell は、SortHeaderRowStyle CSS クラスを使用して書式設定される GridView の幅全体にまたがるように書式設定され、並べ替えグループ名 (Category など) とグループの値 (Beverages など) の両方を表示する Text プロパティを持つことに注意してください。 最後に、lastValuecurrentValue の値に更新されます。

並べ替えグループ ヘッダー行 SortHeaderRowStyle の書式設定に使用する CSS クラスは、Styles.css ファイルで指定する必要があります。 お好きなスタイル設定を自由に使用してください。私は次を使用しました:

.SortHeaderRowStyle
{
    background-color: #c00;
    text-align: left;
    font-weight: bold;
    color: White;
}

現在のコードでは、並べ替えインターフェイスは、任意の BoundField による並べ替え時に並べ替えグループ ヘッダーを追加します (図 5 を参照してください。これは、サプライヤーで並べ替えた場合のスクリーンショットです)。 ただし、他のフィールド型 (CheckBoxField や TemplateField など) で並べ替える場合、並べ替えグループ ヘッダーはどこにも見つかりません (図 6 を参照してください)。

The Sorting Interface Includes Sort Group Headers When Sorting by BoundFields

図 5: 並べ替えインターフェイスには、BoundField による並べ替え時に並べ替えグループ ヘッダーが含まれます (クリックするとフルサイズの画像が表示されます)

The Sort Group Headers are Missing When Sorting a CheckBoxField

図 6: CheckBoxField の並べ替え時は並べ替えグループ ヘッダーがありません (クリックするとフルサイズの画像が表示されます)

CheckBoxField による並べ替え時に並べ替えグループ ヘッダーが見つからない理由は、コードで現在、各行の並べ替えられた列の値を決定するために TableCellText プロパティのみが使用されているためです。 CheckBoxField の場合、TableCellText プロパティは空の文字列です。代わりに、値は、TableCellControls コレクション内にある CheckBox Web コントロールを介して使用できます。

BoundField 以外のフィールド型を処理するには、TableCellControls コレクションに 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 に設定されます。 それ以外の場合、値は TableCellText プロパティから取得されます。 このロジックを複製して、GridView に存在する可能性があるすべての TemplateField の並べ替えを処理できます。

上記のコードの追加により、Discontinued CheckBoxField による並べ替え時に並べ替えグループ ヘッダーが存在するようになりました (図 7 を参照)。

The Sort Group Headers are Now Present When Sorting a CheckBoxField

図 7: CheckBoxField の並べ替え時にグループ ヘッダーの並べ替えが存在するようになりました (クリックするとフルサイズの画像が表示されます)

Note

CategoryIDSupplierID、または 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見つけることができます。