DataList または Repeater コントロールのデータを並べ替える (C#)

作成者: Scott Mitchell

PDF のダウンロード

このチュートリアルでは、DataList と Repeater に並べ替えのサポートを組み込む方法と、データのページングと並べ替えが可能な DataList または Repeater を構築する方法について説明します。

はじめに

前のチュートリアルでは、DataList にページングのサポートを実装する手順を確認しました。 ProductsBLL クラス (GetProductsAsPagedDataSource) に新しいメソッドを作成し、PagedDataSource オブジェクトを返すようにしました。 DataList または Repeater にバインドすると、DataList または Repeater には、要求されたページのデータのみが表示されます。 この手法は、GridView、DetailsView、FormView の各コントロールが既定の組み込みページング機能を提供するために内部で使用しているものと同様です。

GridView は、ページング サポートだけでなく、すぐに使える組み込みの並べ替えの機能も提供しています。 DataList と Repeater のどちらも組み込みの並べ替え機能は組み込まれていませんが、少しのコードを追加することで並べ替え機能を実装できます。 このチュートリアルでは、DataList と Repeater に並べ替えのサポートを組み込む方法と、データのページングと並べ替えが可能な DataList または Repeater を構築する方法について説明します。

並べ替えのレビュー

レポート データのページングと並べ替え」チュートリアルで説明したように、GridView コントロールにはすぐに使える組み込みの並べ替え機能が用意されています。 各 GridView フィールドには、データを並べ替える基準となるデータ フィールドを指定する SortExpression が関連付けられています。 GridView の true プロパティが AllowSorting に設定されている場合、SortExpression プロパティ値の設定されたフィールドのヘッダー部分が LinkButton として表示されます。 ユーザーが特定の GridView フィールドのヘッダーをクリックすると、クリックしたフィールドの SortExpression に基づいてデータが並べ替えられ、ポストバックが発生します。

また、GridView コントロールには SortExpression プロパティがあり、データの並べ替えの基準となる GridView フィールドの SortExpression が格納されます。 加えて、SortDirection プロパティによって、データを昇順で並べ替えるか降順で並べ替えるかが決定されます (ユーザーが GridView フィールドのヘッダー リンクを連続して 2 回クリックすると、並べ替え順序が切り替わります)。

GridView コントロールがデータ ソース コントロールにバインドされると、SortExpression プロパティと SortDirection プロパティがデータ ソース コントロールに渡されます。 データ ソース コントロールは、データを取得し、渡された SortExpression プロパティと SortDirection プロパティに基づいて並べ替えを行います。 データの並べ替えが完了すると、データ ソース コントロールは並べ替えられたデータを GridView に返します。

DataList や Repeater コントロールでこの機能を実現するには、次のことを行う必要があります。

  • 並べ替え用のインターフェイスを作成する
  • 並べ替えの基準となるデータ フィールドと、昇順で並べ替えるか降順で並べ替えるかを記憶しておく
  • ObjectDataSource に特定のデータ フィールドでデータを並べ替えるように指示する

これら 3 つの作業については、手順 3 と手順 4 で取り上げます。 その後、DataList や Repeater でページングと並べ替えの両方をサポートする方法について説明します。

手順 2: Repeater で製品を表示する

並べ替え関連の機能を実装する前に、まず製品を Repeater コントロールに一覧表示することから始めましょう。 まず、PagingSortingDataListRepeater フォルダーの Sorting.aspx ページを開きます。 Web ページに Repeater コントロールを追加し、その ID プロパティを SortableProducts に設定します。 Repeater のスマート タグから、ProductsDataSource という名前の新しい ObjectDataSource を作成し、ProductsBLL クラスの GetProducts() メソッドからデータを取得するように構成します。 [挿入]、[更新]、[削除] の各タブのドロップダウン リストから [(なし)] オプションを選択します。

ObjectDataSource を作成し、GetProductsAsPagedDataSource() メソッドを使用するように構成する

図 1: ObjectDataSource を作成し、GetProductsAsPagedDataSource() メソッドを使用するように構成する (クリックするとフルサイズの画像が表示されます)

[UPDATE]、[INSERT]、[DELETE] タブのドロップダウン リストを [(なし)] に設定する

図 2: [挿入]、[更新]、[削除] タブのドロップダウン リストを [(なし)] に設定する (クリックするとフルサイズの画像が表示されます)

DataList とは異なり、Visual Studio では Repeater コントロールをデータ ソースにバインドしても、自動的に ItemTemplate は作成されません。 また、この ItemTemplate は宣言的に追加する必要があります。Repeater コントロールのスマート タグには、DataList にあるような [テンプレートの編集] オプションがないためです。 前のチュートリアルで使用したのと同じ ItemTemplate を使用しましょう。これは、製品の名前、仕入先、カテゴリを表示するものでした。

ItemTemplate を追加すると、Repeater と ObjectDataSource の宣言型マークアップは次のようになるはずです。

<asp:Repeater ID="SortableProducts" DataSourceID="ProductsDataSource"
    EnableViewState="False" runat="server">
    <ItemTemplate>
        <h4><asp:Label ID="ProductNameLabel" runat="server"
            Text='<%# Eval("ProductName") %>'></asp:Label></h4>
        Category:
        <asp:Label ID="CategoryNameLabel" runat="server"
            Text='<%# Eval("CategoryName") %>'></asp:Label><br />
        Supplier:
        <asp:Label ID="SupplierNameLabel" runat="server"
            Text='<%# Eval("SupplierName") %>'></asp:Label><br />
        <br />
        <br />
    </ItemTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProducts">
</asp:ObjectDataSource>

図 3 は、このページをブラウザーで表示した場合の画面を示しています。

各製品名、仕入先、およびカテゴリが表示されます

図 3: 各製品の名前、仕入先、カテゴリが表示されている (クリックするとフルサイズの画像が表示されます)

手順 3: ObjectDataSource にデータの並べ替えを指示する

Repeater に表示されるデータを並べ替えるには、データの並べ替えに使用する式を ObjectDataSource に伝える必要があります。 ObjectDataSource がデータを取得する前に、まず Selecting イベント が発生します。これにより、並べ替え式を指定する機会が与えられます。 Selecting イベント ハンドラーには ObjectDataSourceSelectingEventArgs 型のオブジェクトが渡され、このオブジェクトには DataSourceSelectArguments 型の Arguments という名前のプロパティがあります。 DataSourceSelectArguments クラスは、データのコンシューマーからデータ ソース コントロールにデータ関連の要求を渡すように設計されており、SortExpression プロパティ が含まれています。

ASP.NET ページから ObjectDataSource に並べ替え情報を渡すには、Selecting イベントのイベント ハンドラーを作成し、次のコードを使用します。

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.Arguments.SortExpression = sortExpression;
}

sortExpression 値には、データの並べ替えに使用するデータ フィールドの名前 (ProductName など) を割り当てる必要があります。 並べ替えの方向に関連するプロパティがないため、データを降順で並べ替えるには、sortExpression 値の末尾に文字列 DESC を追加します (例: ProductName DESC)。

sortExpression にいくつかのハードコーディングされた値を試し、ブラウザーで結果を確認してみてください。 図 4 に示すように、sortExpression として ProductName DESC を使用すると、製品が名前のアルファベットの逆順に並べ替えられます。

製品は、名前のアルファベット順に並べ替えられます。

図 4: 製品が名前のアルファベットの逆順で並べ替えられている (クリックするとフルサイズの画像が表示されます)

手順 4: 並べ替え用のインターフェイスを作成し、並べ替え式と方向を保持する

GridView で並べ替え機能を有効にすると、並べ替え可能な各フィールドのヘッダー テキストが LinkButton に変わり、クリックするとデータがそれに応じて並べ替えられるようになります。 このような並べ替え用のインターフェイスは、データが列に整然と配置されている GridView には適しています。 ただし、DataList や Repeater コントロールでは、別の並べ替え用のインターフェイスが必要です。 データをグリッド形式ではなくリスト形式で表示する場合、データの並べ替えに使用できるフィールドを選択するためのドロップダウン リストが並べ替え用のインターフェイスとしてよく使われます。 このチュートリアルではそのようなインターフェイスを実装してみましょう。

DropDownList Web コントロールを SortableProducts Repeater の上に追加し、その ID プロパティを SortBy に設定します。 プロパティ ウィンドウで Items プロパティの省略記号をクリックし、ListItem コレクション エディターを開きます。 ProductNameCategoryNameSupplierName の各フィールドでデータを並べ替えるための ListItem を追加します。 また、ListItem を追加して、製品名をアルファベットの逆順で並べ替えます。

ListItem Textプロパティは任意の値 (Name など) に設定できますが、Valueプロパティにはデータ フィールドの名前 (ProductName など) を設定する必要があります。 結果を降順で並べ替えるには、データ フィールド名の末尾に文字列 DESC を追加します (例: ProductName DESC)。

並べ替え可能な各データ フィールドの ListItem を追加する

図 5: 並べ替え可能な各データ フィールドに ListItem を追加する

最後に、DropDownList の右側に Button Web コントロールを追加します。 その IDRefreshRepeater に設定し、Text プロパティを Refresh に設定します。

ListItem を作成して [更新] ボタンを追加すると、DropDownList と Button の宣言型構文は次のようになります。

<asp:DropDownList ID="SortBy" runat="server">
    <asp:ListItem Value="ProductName">Name</asp:ListItem>
    <asp:ListItem Value="ProductName DESC">Name (Reverse Order)
        </asp:ListItem>
    <asp:ListItem Value="CategoryName">Category</asp:ListItem>
    <asp:ListItem Value="SupplierName">Supplier</asp:ListItem>
</asp:DropDownList>
<asp:Button runat="server" ID="RefreshRepeater" Text="Refresh" />

並べ替え用の DropDownList が完成したら、次は ObjectDataSource の Selecting イベント ハンドラーを更新し、ハードコーディングされた並べ替え式ではなく、選択された SortBy``ListItemValue プロパティを使用するようにする必要があります。

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    // Have the ObjectDataSource sort the results by the selected
    // sort expression
    e.Arguments.SortExpression = SortBy.SelectedValue;
}

この時点で最初にページにアクセスすると、既定で選択されているSortBy ListItemであるため、製品は最初にProductNameデータ フィールドで並べ替えられます (図 6 を参照)。 カテゴリなどの別の並べ替えオプションを選択して [更新] をクリックすると、ポストバックが発生し、カテゴリ名でデータが再度並べ替えられます (図 7 参照)。

製品は最初に名前で並べ替えられます

図 6: 製品は最初、名前で並べ替えられている (クリックするとフルサイズの画像が表示されます)

製品がカテゴリ別に並べ替えられるようになりました

図 7: 製品がカテゴリ別に並べ替えられるようになった (クリックするとフルサイズの画像が表示されます)

Note

[更新] ボタンをクリックすると、Repeater のビュー ステートが無効になっているため、データが自動的に再度並べ替えられます。これにより、ポストバックが発生するたびに Repeater がデータ ソースに再バインドされるようになります。 Repeater のビュー ステートを有効にしたままにしておくと、並べ替えのドロップダウン リストを変更しても、並べ替え順序には影響しません。 これを修正するには、[更新] ボタンの Click イベントのイベント ハンドラーを作成し、Repeater をデータ ソースに再バインドします (Repeater の DataBind() メソッドを呼び出します)。

並べ替え式と方向を保持する

並べ替えに関係しないポストバックが発生する可能性のあるページで、並べ替え可能な DataList や Repeater を作成する場合、並べ替え式と方向をポストバックの前後で保持しておくことが不可欠です。 たとえば、このチュートリアルの Repeater を更新し、各製品に [削除] ボタンを追加することを想像してみてください。 ユーザーが [削除] ボタンをクリックすると、選択した製品を削除し、データを Repeater に再バインドするコードを実行します。 並べ替えの詳細がポストバックの前後で保持されていない場合、画面に表示されるデータは元の並べ替え順序に戻ります。

このチュートリアルでは、DropDownList が並べ替え式と方向をビュー ステートに暗黙的に保存してくれます。 さまざまな並べ替えオプションを提供する LinkButtons などを備えた別の並べ替え用のインターフェイスを使用している場合は、ポストバックの前後で並べ替え順序を保持するように注意する必要があります。 これは、並べ替えパラメーターをページのビュー ステートに保存するか、並べ替えパラメーターをクエリ文字列に含めるか、または他の状態を保持する手法を用いることで実現できます。

このチュートリアルの今後の例では、ページのビュー ステートに並べ替えの詳細を保持する方法について説明します。

手順 5: 既定のページングを使用する DataList に並べ替えのサポートを追加する

前のチュートリアル では、DataList を使用して既定のページングを実装する方法について説明しました。 前述の例を発展させて、ページングされたデータを並べ替えられるようにしてみましょう。 最初に、PagingSortingDataListRepeater フォルダーの SortingWithDefaultPaging.aspxPaging.aspx のページを開きます。 Paging.aspx ページで [ソース] ボタンをクリックすると、ページの宣言型マークアップが表示されます。 選択したテキスト (図 8 を参照) をコピーし、SortingWithDefaultPaging.aspx<asp:Content> タグ間の宣言型マークアップに貼り付けます。

<asp:Content> タグの宣言型マークアップを Paging.aspx から SortingWithDefaultPaging.aspx にレプリケートする

図 8: Paging.aspx から SortingWithDefaultPaging.aspx までの <asp:Content> タグ内の宣言型マークアップを複製する (クリックするとフルサイズの画像が表示されます)

宣言型マークアップをコピーしたら、Paging.aspx ページの分離コード クラスに含まれているメソッドとプロパティを SortingWithDefaultPaging.aspx の分離コード クラスにコピーします。 次に、ブラウザーで SortingWithDefaultPaging.aspx ページを表示してみてください。 Paging.aspx と同じ機能と外観が表示されるはずです。

既定のページングと並べ替えメソッドを含むように ProductsBLL を拡張する

前のチュートリアルでは、ProductsBLL クラスに PagedDataSource オブジェクトを返す GetProductsAsPagedDataSource(pageIndex, pageSize) メソッドを作成しました。 この PagedDataSource オブジェクトには、すべて の製品が設定されました (BLL の GetProducts() メソッドを使用)。ただし、DataList にバインドすると、指定された pageIndexpageSize の入力パラメーターに対応するレコードのみが表示されました。

このチュートリアルの前半では、ObjectDataSource の Selecting イベント ハンドラーから並べ替え式を指定することで、並べ替えのサポートを追加しました。 これは、GetProducts() メソッドが返す ProductsDataTable のように、並べ替え可能なオブジェクトが ObjectDataSource に返される場合にはうまく機能します。 ただし、GetProductsAsPagedDataSource メソッドが返す PagedDataSource オブジェクトは、内部データ ソースの並べ替えをサポートしていません。 その代わりに、GetProducts() メソッドから返される結果を PagedDataSource に格納するに、並べ替える必要があります。

これを実現するには、ProductsBLL クラスに新しいメソッド GetProductsSortedAsPagedDataSource(sortExpression, pageIndex, pageSize) を作成します。 GetProducts() メソッドが返す ProductsDataTable を並べ替えるには、その既定の DataTableViewSort プロパティを指定します。

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Select, false)]
public PagedDataSource GetProductsSortedAsPagedDataSource
    (string sortExpression, int pageIndex, int pageSize)
{
    // Get ALL of the products
    Northwind.ProductsDataTable products = GetProducts();
    // Sort the products
    products.DefaultView.Sort = sortExpression;
    // Limit the results through a PagedDataSource
    PagedDataSource pagedData = new PagedDataSource();
    pagedData.DataSource = products.DefaultView;
    pagedData.AllowPaging = true;
    pagedData.CurrentPageIndex = pageIndex;
    pagedData.PageSize = pageSize;
    return pagedData;
}

GetProductsSortedAsPagedDataSource メソッドは、前のチュートリアルで作成した GetProductsAsPagedDataSource メソッドとは若干異なります。 具体的には、GetProductsSortedAsPagedDataSource は追加の入力パラメーター sortExpression を受け取り、この値を ProductDataTableDefaultViewSort プロパティに代入します。 数行後のコードでは、PagedDataSource オブジェクトの DataSource に ProductDataTableDefaultView が設定されます。

GetProductsSortedAsPagedDataSource メソッドを呼び出し、SortExpression 入力パラメーターの値を指定する

GetProductsSortedAsPagedDataSource メソッドが完成したので、次はこのパラメーターに値を設定する必要があります。 SortingWithDefaultPaging.aspx の ObjectDataSource は現在、GetProductsAsPagedDataSource メソッドを呼び出すように構成されており、SelectParameters コレクションで指定された 2 つの QueryStringParameters を通じて 2 つの入力パラメーターを渡しています。 この 2 つの QueryStringParameters は、GetProductsAsPagedDataSource メソッドの pageIndex パラメーターと pageSize パラメーターのソースが、クエリ文字列フィールド pageIndexpageSize から取得されることを示しています。

ObjectDataSource の SelectMethod プロパティを更新して、新しい GetProductsSortedAsPagedDataSource メソッドを呼び出すようにします。 次に、新しい QueryStringParameter を追加して、sortExpression 入力パラメーターがクエリ文字列フィールド sortExpression からアクセスされるようにします。 QueryStringParameterDefaultValue を ProductName に設定します。

これらを変更すると、ObjectDataSource の宣言型マークアップは次のようになるはずです。

<asp:ObjectDataSource ID="ProductsDefaultPagingDataSource"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsSortedAsPagedDataSource"
    OnSelected="ProductsDefaultPagingDataSource_Selected" runat="server">
    <SelectParameters>
        <asp:QueryStringParameter DefaultValue="ProductName"
            Name="sortExpression" QueryStringField="sortExpression"
            Type="String" />
        <asp:QueryStringParameter DefaultValue="0" Name="pageIndex"
            QueryStringField="pageIndex" Type="Int32" />
        <asp:QueryStringParameter DefaultValue="4" Name="pageSize"
            QueryStringField="pageSize" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

この時点で、SortingWithDefaultPaging.aspx ページは結果を製品名のアルファベット順に並べ替えます (図 9 を参照)。 これは、既定では ProductName の値が GetProductsSortedAsPagedDataSource メソッドの sortExpression パラメーターとして渡されるためです。

既定では、結果は ProductName で並べ替えられます

図 9: 既定では、結果は ProductName 別に並べ替えられる (クリックするとフルサイズの画像が表示されます)

sortExpression クエリ文字列フィールド (SortingWithDefaultPaging.aspx?sortExpression=CategoryName など) を手動で追加すると、指定された sortExpression で結果が並べ替えられます。 ただし、このクエリ文字列パラメーター sortExpression は、データの別のページに移動する際は含まれません。 実際、[次へ] や [最後] ページ ボタンをクリックすると、Paging.aspx に戻ってしまいます。 さらに、現状では並べ替えのためのインターフェイスが用意されていません。 現状、ユーザーがページングされたデータの並べ替え順を変更するには、クエリ文字列を直接編集するしか方法がありません。

並べ替え用のインターフェイスを作成する

まず、RedirectUser メソッドを更新して、ユーザーを Paging.aspx ではなく SortingWithDefaultPaging.aspx に送信し、sortExpression 値をクエリ文字列に含めるようにする必要があります。 また、読み取り専用のページレベルの名前付き SortExpression プロパティも追加する必要があります。 このプロパティは、前のチュートリアルで作成した PageIndex プロパティと PageSize プロパティと同様、クエリ文字列に sortExpression フィールドが存在する場合はその値を返し、存在しない場合は既定値 (ProductName) を返します。

現状、RedirectUser メソッドが受け取るのは、表示するページのインデックスを指定する 1 つの入力パラメーターのみです。 ただし、クエリ文字列で指定されているものとは異なる並べ替え式を使用して、ユーザーをデータの特定のページにリダイレクトする必要が生じることもあります。 これから、このページの並べ替え用のインターフェイスを作成します。このインターフェイスには、指定された列でデータを並べ替えるための一連の Button Web コントロールが含まれます。 いずれかのボタンがクリックされたら、適切な並べ替え式の値を渡しながらユーザーをリダイレクトさせる必要があります。 この機能を提供するには、RedirectUser メソッドを 2 つのバージョンで実装します。 1 つ目のバージョンは表示対象のページ インデックスのみを受け取るようにし、2 つ目のバージョンはページ インデックスと並べ替え式の両方を受け取るようにします。

private string SortExpression
{
    get
    {
        if (!string.IsNullOrEmpty(Request.QueryString["sortExpression"]))
            return Request.QueryString["sortExpression"];
        else
            return "ProductName";
    }
}
private void RedirectUser(int sendUserToPageIndex)
{
    // Use the SortExpression property to get the sort expression
    // from the querystring
    RedirectUser(sendUserToPageIndex, SortExpression);
}
private void RedirectUser(int sendUserToPageIndex, string sendUserSortingBy)
{
   // Send the user to the requested page with the requested sort expression
   Response.Redirect(string.Format(
      "SortingWithDefaultPaging.aspx?pageIndex={0}&pageSize={1}&sortExpression={2}",
      sendUserToPageIndex, PageSize, sendUserSortingBy));
}

このチュートリアルの最初の例では、DropDownList を使用して並べ替え用のインターフェイスを作成しました。 この例では、DataList の上に配置する 3 つの Button Web コントロールを使うことにします。それぞれ、ProductNameCategoryNameSupplierName を基準に並べ替えるためのボタンです。 3 つの Button Web コントロールを追加し、それらの ID プロパティと Text プロパティを適切に設定します。

<p>
    <asp:Button runat="server" id="SortByProductName"
        Text="Sort by Product Name" />
    <asp:Button runat="server" id="SortByCategoryName"
        Text="Sort by Category" />
    <asp:Button runat="server" id="SortBySupplierName"
        Text="Sort by Supplier" />
</p>

次に、それぞれの Click イベント ハンドラーを作成します。 これらのイベント ハンドラーでは、最初のページに戻るために、それぞれに対応する並べ替え式を使用して RedirectUser メソッドを呼び出す必要があります。

protected void SortByProductName_Click(object sender, EventArgs e)
{
    // Sort by ProductName
    RedirectUser(0, "ProductName");
}
protected void SortByCategoryName_Click(object sender, EventArgs e)
{
    // Sort by CategoryName
    RedirectUser(0, "CategoryName");
}
protected void SortBySupplierName_Click(object sender, EventArgs e)
{
    // Sort by SupplierName
    RedirectUser(0, "SupplierName");
}

ページに最初にアクセスした際は、データは製品名のアルファベット順に並べ替えられています (図 9 を参照)。 [次へ] ボタンをクリックしてデータの 2 ページ目に進み、[カテゴリ別に並べ替え] ボタンをクリックします。 これにより、カテゴリ名で並べ替えられたデータの最初のページに戻ります (図 10 を参照)。 同様に、[仕入先別に並べ替え] ボタンをクリックすると、データの最初のページから仕入先別にデータが並べ替えられます。 データがページングされても、並べ替えの選択内容は保持されます。 図 11 は、カテゴリで並べ替えたあと、データの 13 ページ目に移動した際のページを示しています。

製品はカテゴリ別に並べ替えられます

図 10: 製品がカテゴリ別に並べ替えられている (クリックするとフルサイズの画像が表示されます)

並べ替え式は、データをページングするときに記憶されます

図 11: データのページング時に並べ替え式が保持される (クリックするとフルサイズの画像が表示されます)

手順 6: Repeater でのレコードのカスタム ページング

手順5 で確認した DataList の例では、非効率的な既定のページング手法を用いてデータのページング処理を行っています。 大量のデータをページングする場合には、カスタム ページングを使用することが不可欠となります。 大量のデータを効率的にページングするカスタム ページングを適用したデータを並べ替えるに関するチュートリアルでは、既定のページングとカスタム ページングの違いを考察し、カスタム ページングの利用とカスタム ページングされたデータの並べ替えを実現するメソッドを BLL 内に実装しました。 具体的には、これら 2 つの前述のチュートリアルで、ProductsBLL クラスに次の 3 つのメソッドを追加しました。

  • GetProductsPaged(startRowIndex, maximumRows) は、startRowIndex から始まり、maximumRows を超えない特定のレコード サブセットを返します。
  • GetProductsPagedAndSorted(sortExpression, startRowIndex, maximumRows) は、指定された sortExpression 入力パラメーターで並べ替えられた特定のレコード サブセットを返します。
  • TotalNumberOfProducts() は、Products データベース テーブル内のレコードの総数を返します。

これらのメソッドを使用すると、DataList や Repeater コントロールでデータを効率的にページングして並べ替えることができます。 このことを説明するために、まずカスタム ページング サポートを使用して Repeater コントロールを作成してみましょう。その後、並べ替え機能を追加します。

PagingSortingDataListRepeater フォルダーの SortingWithCustomPaging.aspx ページを開き、Repeater をページに追加して、その ID プロパティを Products に設定します。 Repeater のスマート タグから、ProductsDataSource という名前の新しい ObjectDataSource を作成します。 ProductsBLL クラスの GetProductsPaged メソッドからデータを選択するように構成します。

ProductsBLL クラスの GetProductsPaged メソッドを使用するように ObjectDataSource を構成する

図 12: ObjectDataSource を ProductsBLL クラスの GetProductsPaged メソッドを使用するように構成する (クリックするとフルサイズの画像が表示されます)

[挿入]、[更新]、[削除] の各タブのドロップダウン リストを [(なし)] に設定し、[次へ] ボタンをクリックします。 データ ソースの構成ウィザードで、GetProductsPaged メソッドの startRowIndex 入力パラメーターと maximumRows 入力パラメーターのソースを入力するよう求められます。 実際には、これらの入力パラメーターは無視されます。 代わりに、startRowIndexmaximumRows の値は、このチュートリアルの最初のデモで sortExpression を指定したのと同様に、ObjectDataSource の Selecting イベント ハンドラーの Arguments プロパティを通じて渡されます。 したがって、ウィザードのパラメーター ソース ドロップダウン リストは None に設定したままにしておきます。

パラメーター ソースを [なし] に設定したままにする

図 13: パラメーター ソースを [なし] に設定したままにする (クリックするとフルサイズの画像が表示されます)

Note

ObjectDataSource の EnablePaging プロパティを true に設定しない でください。 この設定を行うと、ObjectDataSource は独自の startRowIndex パラメーターと maximumRows パラメーターを SelectMethod の既存のパラメーター リストに自動的に追加してしまいます。 EnablePaging プロパティは、カスタム ページングされたデータを GridView、DetailsView、FormView コントロールにバインドする際に便利です。これらのコントロールは、ObjectDataSource に特定の動作を期待しますが、その動作が利用可能となるのは EnablePaging プロパティが true の場合のみだからです。 DataList と Repeater では、ページングのサポートを手動で追加する必要があるため、このプロパティは false (既定値) に設定したままにしておきます。必要な機能は ASP.NET ページ内に直接組み込むためです。

最後に、製品の名前、カテゴリ、仕入先が表示されるように、Repeater の ItemTemplate を定義します。 これらを変更すると、Repeater と ObjectDataSource の宣言型構文は次のようになるはずです。

<asp:Repeater ID="Products" runat="server" DataSourceID="ProductsDataSource"
    EnableViewState="False">
    <ItemTemplate>
        <h4><asp:Label ID="ProductNameLabel" runat="server"
            Text='<%# Eval("ProductName") %>'></asp:Label></h4>
        Category:
        <asp:Label ID="CategoryNameLabel" runat="server"
            Text='<%# Eval("CategoryName") %>'></asp:Label><br />
        Supplier:
        <asp:Label ID="SupplierNameLabel" runat="server"
            Text='<%# Eval("SupplierName") %>'></asp:Label><br />
        <br />
        <br />
    </ItemTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProductsPaged" TypeName="ProductsBLL">
    <SelectParameters>
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

ブラウザーからページにアクセスし、レコードが返されないことを確認してください。 これは、startRowIndex パラメーター値と maximumRows パラメーター値をまだ指定していないので、両方のパラメーターに 0 の値が渡されているためです。 これらの値を指定するには、ObjectDataSource の Selecting イベントのイベント ハンドラーを作成し、それぞれのパラメーター値をプログラムでハードコードされた値として 0 と 5 に設定します。

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.InputParameters["startRowIndex"] = 0;
    e.InputParameters["maximumRows"] = 5;
}

この変更により、ブラウザーでページを表示すると、最初の 5 つの製品が表示されるようになります。

最初の 5 つのレコードが表示される

図 14: 最初の 5 つのレコードが表示される (クリックするとフルサイズの画像が表示されます)

Note

図 14 に示された製品リストが製品名順に並んでいるのは、効率的なカスタム ページング クエリを実装した GetProductsPaged ストアド プロシージャが、その結果を ProductName によって並べ替えているためです。

ユーザーがページ間を移動できるようにするには、開始行インデックスと最大行数を追跡し、これらの値をポストバック間で保持しておく必要があります。 既定のページングの例では、これらの値を保持するためにクエリ文字列フィールドを使用しました。このデモでは、この情報をページのビュー ステートに保存しましょう。 次の 2 つのプロパティを作成します。

private int StartRowIndex
{
    get
    {
        object o = ViewState["StartRowIndex"];
        if (o == null)
            return 0;
        else
            return (int)o;
    }
    set
    {
        ViewState["StartRowIndex"] = value;
    }
}
private int MaximumRows
{
    get
    {
        object o = ViewState["MaximumRows"];
        if (o == null)
            return 5;
        else
            return (int)o;
    }
    set
    {
        ViewState["MaximumRows"] = value;
    }
}

次に、Selecting イベント ハンドラーのコードを更新して、ハードコードされた値 0 と 5 の代わりに StartRowIndex プロパティと MaximumRows プロパティを使用するようにします。

e.InputParameters["startRowIndex"] = StartRowIndex;
e.InputParameters["maximumRows"] = MaximumRows;

この時点では、ページにはまだ最初の 5 つのレコードしか表示されていません。 ただし、これらのプロパティを設定したことで、ページング インターフェイスを作成する準備が整いました。

ページング インターフェイスを追加する

現在表示されているデータのページ番号と全ページ数を表示する Label Web コントロールを含め、既定のページング例で使用したのと同じ [最初]、[前へ]、[次へ]、[最後] のページング用インターフェイスを使用してみましょう。 Repeater の下に 4 つの Button Web コントロールと Label を追加します。

<p>
    <asp:Button runat="server" ID="FirstPage" Text="<< First" />
    <asp:Button runat="server" ID="PrevPage" Text="< Prev" />
    <asp:Button runat="server" ID="NextPage" Text="Next >" />
    <asp:Button runat="server" ID="LastPage" Text="Last >>" />
</p>
<p>
    <asp:Label runat="server" ID="CurrentPageNumber"></asp:Label>
</p>

次に、4 つのボタンの Click イベント ハンドラーを作成します。 これらのボタンのいずれかがクリックされたら、StartRowIndex を更新し、データを Repeater に再バインドする必要があります。 [最初]、[前へ]、[次へ] ボタンのコードは比較的単純ですが、[最後] ボタンについては、データの最後のページの開始行インデックスをどのように決定すればよいでしょうか。 このインデックスを計算し、[次へ] と [最後] ボタンを有効にするかどうかを判断できるようにするには、ページングされているレコードの合計数を把握する必要があります。 これは、ProductsBLL クラスの TotalNumberOfProducts() メソッドを呼び出すことで判断できます。 TotalNumberOfProducts() メソッドの結果を返す、TotalRowCount という名前の読み取り専用のページ レベルのプロパティを作ってみましょう。

private int TotalRowCount
{
    get
    {
        // Return the value from the TotalNumberOfProducts() method
        ProductsBLL productsAPI = new ProductsBLL();
        return productsAPI.TotalNumberOfProducts();
    }
}

このプロパティを使用すると、最後のページの開始行インデックスを決定できるようになります。 具体的には、TotalRowCount から 1 を引いた値を MaximumRows で割り、MaximumRows を掛けた整数の結果になります。 これで、ページング インターフェイスの 4 つのボタンの Click イベント ハンドラーを記述できます。

protected void FirstPage_Click(object sender, EventArgs e)
{
    // Return to StartRowIndex of 0 and rebind data
    StartRowIndex = 0;
    Products.DataBind();
}
protected void PrevPage_Click(object sender, EventArgs e)
{
    // Subtract MaximumRows from StartRowIndex and rebind data
    StartRowIndex -= MaximumRows;
    Products.DataBind();
}
protected void NextPage_Click(object sender, EventArgs e)
{
    // Add MaximumRows to StartRowIndex and rebind data
    StartRowIndex += MaximumRows;
    Products.DataBind();
}
protected void LastPage_Click(object sender, EventArgs e)
{
    // Set StartRowIndex = to last page's starting row index and rebind data
    StartRowIndex = ((TotalRowCount - 1) / MaximumRows) * MaximumRows;
    Products.DataBind();
}

最後に、データの最初のページを表示している場合はページング インターフェイスの [最初] と [前へ] ボタンを無効にし、最後のページを表示している場合は [次へ] と [最後] ボタンを無効にする必要があります。 この機能を実装するには、ObjectDataSource の Selecting イベント ハンドラーに次のコードを追加します。

// Disable the paging interface buttons, if needed
FirstPage.Enabled = StartRowIndex != 0;
PrevPage.Enabled = StartRowIndex != 0;
int LastPageStartRowIndex = ((TotalRowCount - 1) / MaximumRows) * MaximumRows;
NextPage.Enabled = StartRowIndex < LastPageStartRowIndex;
LastPage.Enabled = StartRowIndex < LastPageStartRowIndex;

これらの Click イベント ハンドラーと、現在の開始行インデックスに基づいてページング インターフェイス要素を有効化または無効化するコードを追加したら、ブラウザーでページをテストします。 図 15 に示すように、初めてこのページにアクセスすると、[最初] ボタンと [前へ] ボタンが無効になっています。 [次へ] をクリックするとデータの 2 ページ目が表示され、[最後] をクリックすると最後のページが表示されます (図 16 と 17 を参照)。 データの最後のページを表示している際は、[次へ] ボタンと [最後] ボタンの両方が無効になっています。

製品の最初のページを表示すると、[前へ] ボタンと [最後] ボタンが無効になる

図 15: 製品の最初のページを表示しているときは [前へ] と [最後] ボタンが無効になる (クリックするとフルサイズの画像が表示されます)

商品の2ページ目が表示されます

図 16: 製品の 2 ページ目が表示される (クリックするとフルサイズの画像が表示されます)

[最後に表示] をクリックすると、データの最終ページが表示されます

図 17: [最後] をクリックするとデータの最後のページが表示される (クリックするとフルサイズの画像が表示されます)

手順 7: カスタム ページングされた Repeater に並べ替えサポートを含める

カスタム ページングが実装されたので、並べ替えサポートを追加する準備が整いました。 ProductsBLL クラスの GetProductsPagedAndSorted メソッドには、GetProductsPaged と同じ startRowIndex 入力パラメーターと maximumRows 入力パラメーターがありますが、さらに sortExpression という追加の入力パラメーターを指定できます。 SortingWithCustomPaging.aspx から GetProductsPagedAndSorted メソッドを使用するには、次の手順を実行する必要があります。

  1. ObjectDataSource の SelectMethod プロパティを GetProductsPaged から GetProductsPagedAndSorted に変更します。
  2. ObjectDataSource の SelectParameters コレクションに sortExpression Parameter オブジェクトを追加します。
  3. ポストバック時にも値を保持できるように、ページのビュー ステートを利用したプライベートな SortExpression プロパティをページレベルで作成します。
  4. ObjectDataSource の Selecting イベント ハンドラーを更新し、ObjectDataSource の sortExpression パラメーターにページ レベルの SortExpression プロパティの値を設定します。
  5. 並べ替え用のインターフェイスを作成します。

まず、ObjectDataSource の SelectMethod プロパティを更新し、 sortExpression Parameterを追加します。 sortExpression ParameterTypeプロパティがStringに設定されていることを確認します。 最初の 2 つのタスクを完了すると、ObjectDataSource の宣言型マークアップは次のようになります。

<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsPagedAndSorted"
    OnSelecting="ProductsDataSource_Selecting">
    <SelectParameters>
        <asp:Parameter Name="sortExpression" Type="String" />
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

次に、値がビュー ステートにシリアル化されるページ レベルの SortExpression プロパティが必要です。 並べ替え式の値が設定されていない場合は、ProductName を既定値として使用します。

private string SortExpression
{
    get
    {
        object o = ViewState["SortExpression"];
        if (o == null)
            return "ProductName";
        else
            return o.ToString();
    }
    set
    {
        ViewState["SortExpression"] = value;
    }
}

ObjectDataSource が GetProductsPagedAndSorted メソッドを呼び出す前に、 sortExpression Parameter SortExpression プロパティの値を設定する必要があります。 Selecting イベント ハンドラーに、次のコード行を追加します。

e.InputParameters["sortExpression"] = SortExpression;

あとは並べ替え用のインターフェイスを実装するだけです。 前例と同様に、製品名、カテゴリ、または仕入先を基準に結果を並べ替えることができる 3 つの Button Web コントロールを使用して、並べ替え用のインターフェイスを実装してみましょう。

<asp:Button runat="server" id="SortByProductName"
    Text="Sort by Product Name" />
<asp:Button runat="server" id="SortByCategoryName"
    Text="Sort by Category" />
<asp:Button runat="server" id="SortBySupplierName"
    Text="Sort by Supplier" />

この 3 つの Button コントロールの Click イベント ハンドラーを作成します。 イベント ハンドラーでは、StartRowIndex を 0 にリセットし、SortExpression を適切な値に設定して、データを Repeater に再バインドします。

protected void SortByProductName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "ProductName";
    Products.DataBind();
}
protected void SortByCategoryName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "CategoryName";
    Products.DataBind();
}
protected void SortBySupplierName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "CompanyName";
    Products.DataBind();
}

以上で終了です。 カスタム ページングと並べ替えの実装には多くの手順が必要でしたが、これらの手順は既定のページングに必要な手順とほぼ同じでした。 図 18 は、カテゴリ別に並べ替えられたデータの最後のページに表示される製品を示しています。

カテゴリ別に並べ替えられたデータの最後のページが表示されます

図 18: カテゴリ別に並べ替えられたデータの最後のページが表示される (クリックするとフルサイズの画像が表示されます)

Note

前の例では、仕入先で並べ替える場合、SupplierName が並べ替え式として使用されました。 ただし、カスタム ページングの実装では "CompanyName" を使う必要があります。 これは、カスタム ページングを実装する GetProductsPagedAndSorted ストアド プロシージャが並べ替え式を ROW_NUMBER() キーワードに渡すためです。ROW_NUMBER() キーワードには、エイリアスではなく実際のカラム名を指定する必要があります。 したがって、並べ替え式には、SELECT クエリ (SupplierName) で使われているエイリアスではなく、Suppliers テーブルのカラム名 (CompanyName) を使用する必要があります。

まとめ

DataList も Repeater も組み込みの並べ替えサポートは提供していませんが、少しコードを記述してカスタムの並べ替え用のインターフェイスを使用することで、そのような機能を追加できます。 並べ替えを実装する一方でページング機能は実装しない場合、並べ替え式は、ObjectDataSource の Select メソッドに渡される DataSourceSelectArguments オブジェクトから指定できます。 この DataSourceSelectArguments オブジェクトの SortExpression プロパティは、ObjectDataSource の Selecting イベント ハンドラーで設定できます。

すでにページング サポートを提供している DataList や Repeater に並べ替え機能を追加する最も簡単な方法は、ビジネス ロジック レイヤーをカスタマイズして、並べ替え式を受け取るメソッドを含める方法です。 この情報は、ObjectDataSource の SelectParameters のパラメーターとして渡すことができます。

このチュートリアルで、DataList と Repeater コントロールを使用したページングと並べ替えの説明を終了します。 次回の最終チュートリアルでは、DataList と Repeater のテンプレートに Button Web コントロールを追加して、アイテムごとにユーザーが実行できるカスタム機能を提供する方法について説明します。

プログラミングに満足!

著者について

7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジに取り組んでいます。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は David Suru でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。