ASP.NET Web Forms GridViewのページングが1ページ目から動かない原因と解決策(PagerTemplateのDropDownListでページ切替)

ASP.NET Web Forms の GridView で PagerTemplate に DropDownList を置いたのに、ページを選んでも常に1ページ目のまま。SqlDataSource から手動 DataBind に切り替えた途端に起きる典型的な原因と、確実に直す実装パターンを整理します。

目次

問題の概要:PagerTemplate の DropDownList でページ選択したい

GridView のページング UI は、標準の「次へ」「前へ」だけでなく、PagerTemplate を使うと自由に作り込めます。よくある要望が「DropDownList でページ番号を選び、任意ページへジャンプしたい」です。

ところが、次のような変更をした直後に「どのページを選んでも 1 ページ目から動かない」症状にぶつかることがあります。

  • 以前は SqlDataSource を使い、GridView には DataSourceID を設定していた
  • 変更後は Page_Load などで DB から取得し、GridView.DataSource → DataBind() する「手動バインド」にした
  • ついでに AutoGenerateColumns="False" にして、BoundField などで列を自前定義した

この状態で DropDownList_SelectedIndexChanged は発火しているのに、見た目が変わらない(ページが切り替わらない)――これが今回のテーマです。

症状:イベントは動くのにページが切り替わらない

代表的な症状は次のとおりです。

  • PagerTemplate 内の DropDownList の選択を変えると、SelectedIndexChanged は確実に呼ばれる
  • しかし GridView の表示は変わらず、常に 1 ページ目のデータが表示され続ける
  • GridView の DataBound が「もう一度呼ばれるはず」と思っていたが、呼ばれない
  • ページ数(PageCount)や現在ページ(PageIndex)の値をデバッグすると、どこかで想定とズレている

結論から言うと、「ページ番号(PageIndex)を変更したあとに、データを再バインドしていない」ことが原因であるケースがほとんどです。SqlDataSource を使っていたときは暗黙に成立していた前提が、手動バインドでは成立しません。

原因の本質:SqlDataSource は“勝手に再バインド”してくれていた

SqlDataSource(DataSourceID)構成が「楽」なのは、ページ移動などの操作に合わせて データの再取得と再バインド を GridView/DataSourceControl 側が面倒を見てくれる点です。つまり開発者は UI をいじっても、裏側の再描画が自然に追従していました。

一方で、SqlDataAdapter などで取得して GridView.DataSource に入れる方式では、ページ移動=表示データの作り直しを自分でやる必要があります。ここが意外と盲点になります。

項目SqlDataSource(DataSourceID)手動バインド(DataSource → DataBind)
ページ移動時の再取得GridView が DataSourceControl を通じて選択処理を呼び出しやすい自分で DB から取得するか、キャッシュしたデータを取り出す必要がある
ページ移動時の再バインド内部で再バインドされることが多く、開発者の意識が薄くなりがち必ず自分で DataBind を呼ぶ(呼ばないと表示が更新されない)
PagerTemplate 内コントロール再バインドにより毎回作り直される(結果として整合が取りやすい)同様に作り直されるが、作り直すタイミングを自分で設計する必要がある
「1ページ目から動かない」原因起きにくいページ変更後に再バインドしない/再バインドの順序が逆

GridView 標準ページングの前提:ページ移動のたびに“作り直す”

GridView のページングは、根本的には次の思想でできています。

  • 現在ページは PageIndex が保持する
  • 表示内容は DataBind() によって生成される
  • つまり PageIndex を変えたら、同じデータ(または該当ページのデータ)で再度 DataBind して初めて表示が変わる

PagerTemplate の DropDownList も同じです。DataBind のたびに Pager 行ごと作り直されるため、次の 2 点が必ず発生します。

  • ページ一覧(DropDownList の Items)の再作成
  • 現在ページ(SelectedValue / SelectedIndex)の復元

まず確認したい落とし穴:Page_Load で毎回 DataBind していないか

よくある失敗が「ページングや選択変更のイベントより先に、Page_Load で毎回 DataBind してしまう」パターンです。Web Forms のイベント順序では、Page_Load のあとに各種イベント(SelectedIndexChanged など)が動きます。つまり、Page_Load で無条件に DataBind すると、イベントで PageIndex を変える前の状態で GridView が作られてしまい、結果として “いつも同じ表示” になりやすいです。

やりがち起きることおすすめ
Page_Load で毎回 LoadGrid();イベント前に描画が固まり、イベント後に再バインドがないと表示が更新されない初回のみ if (!IsPostBack) で LoadGrid()
イベント内で PageIndex を変えるだけPageIndex の値は変わっても、表示データが更新されないイベント内で LoadGrid() を呼んで再バインド

一言でまとめると、「初回は Page_Load(IsPostBack で判定)」「ページ変更はイベント内で再バインド」が基本です。

解決の基本:PageIndex を変えたら必ず再バインドする

DropDownList でページを選んだ瞬間、あなたがやるべき処理は大きく 3 つです。

  • 選択されたページ番号を取り出す
  • GridView.PageIndex に反映する
  • 同じ条件でデータを再取得(または取り出し)して DataBind() する

さらに PagerTemplate の DropDownList は DataBind のたびに作り直されるので、DataBound などで Items を再構築し、現在ページの選択状態を戻します。

実装の考え方:処理のタイミングを“イベント順”で押さえる

Web Forms のポストバックは「画面が一度消えて、サーバー側でコントロールツリーを復元し、イベントを処理し、最後に HTML を再生成する」という流れです。ページングが絡む場合は、次の表のどこで何をするかを意識すると迷いません。

タイミングここで起きることここでやるべきこと
Page_Loadページ表示の入り口。ポストバックか初回かが分かる初回のみ LoadGrid() を呼ぶ(if (!IsPostBack))
各種イベント(SelectedIndexChanged / PageIndexChanging など)ユーザー操作に応じて発火PageIndex を更新し、必ず LoadGrid() を呼ぶ
GridView.DataBoundデータバインドが完了し、PageCount が確定した状態PagerTemplate 内の DropDownList を Items から作り直し、選択状態を反映

特に PageCount は DataBind しないと確定しないため、DropDownList の Items を作るなら DataBound が安全です。

ASPX の例:PagerTemplate に DropDownList を置く

まずは PagerTemplate 側の最小構成です。ポイントは AutoPostBack="True" と、イベントハンドラの指定です。

<asp:GridView ID="myGridView" runat="server"
    AllowPaging="True"
    PageSize="20"
    AutoGenerateColumns="False"
    OnDataBound="myGridView_DataBound"
    OnPageIndexChanging="myGridView_PageIndexChanging">

    <Columns>
        <asp:BoundField DataField="Id" HeaderText="ID" />
        <asp:BoundField DataField="Name" HeaderText="名前" />
        <asp:BoundField DataField="UpdatedAt" HeaderText="更新日" DataFormatString="{0:yyyy/MM/dd}" />
    </Columns>

    <PagerTemplate>
        <span>ページ:</span>
        <asp:DropDownList ID="PageDropDownList" runat="server"
            AutoPostBack="True"
            OnSelectedIndexChanged="PageDropDownList_SelectedIndexChanged" />
        <span>&nbsp;(<asp:Label ID="CurrentPageLabel" runat="server" />)</span>
    </PagerTemplate>
</asp:GridView>

AutoGenerateColumns を True/False にしても、ページングが動かない直接原因にはなりません。ここで重要なのは「ページを変えた後に、GridView を再バインドしているか」です。

C# の例:LoadGrid と Pager 再構築を分離して確実に動かす

次のコードは、手動バインドでページングを安定させる“王道パターン”です。

  • LoadGrid() に「取得→バインド」だけを集約する
  • ページ変更イベント(DropDownList / PageIndexChanging)では PageIndex を更新して LoadGrid()
  • DataBound で PagerTemplate 内の DropDownList を再構築し、選択状態を復元する
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        myGridView.PageIndex = 0; // 初期ページ
        LoadGrid();
    }
}

private void LoadGrid()
{
    // 1) DB から取得(例:DataTable)
    var dt = GetData();

    // 2) GridView にバインド
    myGridView.DataSource = dt;
    myGridView.DataBind();
}

// 例:SqlDataAdapter などで DataTable を作る(実装はプロジェクトに合わせてください)
private DataTable GetData()
{
    // ここでは雛形だけ示します
    var dt = new DataTable();

    // TODO: SqlConnection/SqlCommand/SqlDataAdapter で dt を埋める
    // 重要:ページングで表示順が変わらないように、SQL には ORDER BY を付ける

    return dt;
}

// 標準のページャ(次へ/前へ)や、コードからの PageIndex 変更に対応
protected void myGridView_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    myGridView.PageIndex = e.NewPageIndex;
    LoadGrid();
}

// PagerTemplate 内の DropDownList を作り直す(DataBind のたびに呼ばれる)
protected void myGridView_DataBound(object sender, EventArgs e)
{
    BindPagerDropDown();
}

// DropDownList でページを選んだとき
protected void PageDropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
    var ddl = (DropDownList)sender;

    // Value に 0 始まりの PageIndex を入れておく前提
    int newPageIndex;
    if (!int.TryParse(ddl.SelectedValue, out newPageIndex))
        return;

    // 念のため範囲チェック(データ件数が変動する場合に安全)
    if (newPageIndex < 0) newPageIndex = 0;
    if (newPageIndex > myGridView.PageCount - 1) newPageIndex = myGridView.PageCount - 1;

    myGridView.PageIndex = newPageIndex;

    // ★最重要:PageIndex を変えたら必ず再バインド
    LoadGrid();
}

private void BindPagerDropDown()
{
    var pagerRow = myGridView.BottomPagerRow ?? myGridView.TopPagerRow;
    if (pagerRow == null) return;

    var ddl = pagerRow.FindControl("PageDropDownList") as DropDownList;
    var lbl = pagerRow.FindControl("CurrentPageLabel") as Label;
    if (ddl == null) return;

    ddl.Items.Clear();

    // PageCount は DataBind 後に確定する
    for (int i = 0; i < myGridView.PageCount; i++)
    {
        int pageNumber = i + 1;
        // 表示は 1 始まり、Value は 0 始まりの PageIndex にすると混乱しにくい
        ddl.Items.Add(new ListItem(pageNumber.ToString(), i.ToString()));
    }

    // 現在ページを反映
    ddl.SelectedValue = myGridView.PageIndex.ToString();

    if (lbl != null)
    {
        lbl.Text = string.Format("{0} / {1}", myGridView.PageIndex + 1, myGridView.PageCount);
    }
}

この形にしておくと、次のメリットがあります。

  • 「どのイベントで再バインドすべきか」が明確になる
  • PagerTemplate のコントロール再生成に引きずられず、常に現在ページが正しく表示される
  • DropDownList だけでなく、標準のページャ(次へ/前へ)も同じロジックで動く

「DataBound が呼ばれない」本当の意味

質問でよく出てくるのが「SelectedIndexChanged は動くのに DataBound がもう一度呼ばれない」という状況です。これは、ほぼ次のどちらかです。

  • そもそも DataBind() を呼んでいない(PageIndex だけ変えて満足している)
  • Page_Load の無条件 DataBind() がイベント前に走り、イベント後の再バインドがない

DataBound は “DataBind の結果として” 発火します。逆に言えば、DataBind を呼ばない限り DataBound は増えません。イベントの発火と DataBound の発火は別物なので、混同しないのが近道です。

よくある落とし穴とチェックリスト

同じ「1ページ目から動かない」でも、原因が複数重なっていることがあります。切り分けに使えるチェックリストをまとめます。

症状ありがちな原因確認ポイント/対処
DropDownList のイベントが発火しないAutoPostBack が False、またはイベント未設定ASPX で AutoPostBack="True" と OnSelectedIndexChanged を確認。UpdatePanel 内ならトリガーも確認
イベントは発火するが表示が変わらないイベント後に DataBind していないイベント内に LoadGrid()(再取得+DataBind)があるか確認
FindControl で DropDownList が取れないPagerRow が未生成/TopPagerRow だけ表示/ID違いBottomPagerRow ?? TopPagerRow の両方を見る。ID は PageDropDownList と一致させる
ページ数が 1 のときだけおかしいPageCount が 1 で Items が 1 つ、SelectedValue の不整合DataBound で必ず Items を作り直し、SelectedValue に PageIndex を設定
途中でデータ件数が変わると例外現在の PageIndex が範囲外になるPageIndex を 0~PageCount-1 に丸める(範囲チェック)
並び順がページごとに変わるSQL に ORDER BY がない(取得順が不定)ページングの前提として、必ず安定した ORDER BY を付ける

デバッグのときは、イベント内で以下の値をウォッチすると状況が一気に見えます。

  • myGridView.PageIndex(イベント前後でどう変わるか)
  • myGridView.PageCount(DataBind 後に確定しているか)
  • myGridView.Rows.Count(バインド結果が出ているか)
  • PagerRow が null かどうか(BottomPagerRow / TopPagerRow)

運用上の注意:標準ページングは“全件取得→メモリで分割”になりやすい

GridView の標準ページングは、データソース側が「ページング対応の Select」を提供していない限り、いったん全件を取ってから サーバー側でページ分割して表示する構造になりがちです。件数が増えると、次のような問題が出やすくなります。

  • ページをめくるたびに全件取得してしまい、DB と Web サーバーの負荷が跳ねる
  • DataTable が大きくなり、メモリと GC の負荷が増える
  • 同時アクセスが増えるとレスポンスが急に悪化する
データ件数の目安おすすめ方針理由
数百件程度全件取得+GridView 標準ページングでも許容されることが多い実装が簡単で、体感速度も維持しやすい
数千件以上SQL 側でページング(OFFSET/FETCH など)を検討ページサイズ分だけ取得でき、負荷が安定する
検索条件が複雑/アクセスが多いサーバー側ページング+インデックス設計(ORDER BY に効くキー)「速いページング」は SQL の設計が鍵になる

SQL Server のページング例:OFFSET/FETCH を使って“必要な分だけ”取る

データ件数が大きい場合は、ページ番号とページサイズをパラメータとして SQL に渡し、該当ページだけを取得する方が安定します。SQL Server の一例を示します(実際のテーブル名・列名に置き換えてください)。

SELECT
    Id,
    Name,
    UpdatedAt
FROM dbo.YourTable
WHERE IsDeleted = 0
ORDER BY UpdatedAt DESC, Id DESC
OFFSET (@PageIndex * @PageSize) ROWS
FETCH NEXT @PageSize ROWS ONLY;

この方式なら、GridView のページング操作ごとに取得する行数は PageSize 程度に抑えられます。総件数(ページ数)を出すには、別クエリで COUNT を取るか、要件に応じて COUNT(*) OVER() を使うなど工夫します。

キャッシュで DB 負荷を抑える場合の選択肢

「ページングするたびに同じ検索結果を DB から取り直すのがもったいない」という場合、取得結果を一時的に保持する手もあります。ただし保持場所によって特徴が違うので、安易に ViewState に詰め込むのはおすすめしません。

保持場所メリットデメリット向いているケース
ViewState実装が手軽ページサイズが巨大化しやすい(通信量増)少量データで、どうしても再取得したくない
Sessionクライアントへの転送が増えないセッション数が増えるとメモリを圧迫社内ツールなど、同時ユーザーが少ない
サーバー側ページング(SQL)最もスケールしやすい実装・設計が少し増える件数が多い/アクセスが多い/長期運用

まとめ:手動バインドにしたら“ページ変更後の再バインド”が必須

PagerTemplate の DropDownList が反応するのに 1 ページ目から動かない問題は、ほとんどの場合 「PageIndex を変えた後に DataBind していない」、または 「Page_Load の無条件 DataBind で順序が崩れている」ことが原因です。

  • 初回は if (!IsPostBack) LoadGrid();
  • ページ変更(DropDownList / PageIndexChanging)では PageIndex を更新して LoadGrid();
  • DataBound で DropDownList の Items を作り直し、現在ページを反映

この 3 点を押さえておけば、SqlDataSource から手動バインドへ移行しても、PagerTemplate のページジャンプは安定して動作します。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次