DataGridView:スクロール位置を保存・復元する方法(サンプルあり)

📋 目次(クリックで展開)

DataGridView でデータを再読み込み(リフレッシュ)した際、画面が最上部まで勝手にスクロールしてしまい、ユーザーが直前まで見ていた位置を見失ってしまうことがあります。操作性を維持するためには、データを更新する前に現在のスクロール位置を記憶しておき、更新完了後に元の位置へ戻す(復元する)処理が必要です。

DataGridView にはスクロールバーの位置を直接数値で指定するプロパティは用意されていませんが、現在表示されているセルの位置やオフセット(ズレ量)を制御するプロパティを利用することで、スクロール位置の保存と復元が可能になります。

今回は、2つのボタンを使って、現在の水平・垂直スクロール位置を保存し、任意のタイミングで復元する実装手順について解説します。

スクロール位置を保存・復元する仕組み

DataGridView のスクロール位置は、上下(垂直)と左右(水平)で管理しているプロパティの単位が異なります。

  • 垂直スクロール(上下)FirstDisplayedScrollingRowIndex プロパティを使用します。これは「現在画面の最上部に表示されている行のインデックス(行番号)」を表します。
  • 水平スクロール(左右)HorizontalScrollingOffset プロパティを使用します。これは「画面の左端からどれだけピクセル単位で右にスクロールしているか」を表します。

保存時にはこれらの値を整数(int)の変数に退避させ、復元時にはそれぞれのプロパティに値を書き戻すことで、元の表示位置を再現します。

実装コード

以下は、100行・20列のダミーデータを表示した DataGridView に対し、「保存」ボタンをクリックした瞬間のスクロール状態を記憶し、「復元」ボタンをクリックした際に対象の位置へ画面を自動スクロールさせるコード例です。

Form1.cs

Form1.cs
using System.Data;
namespace DataGridView_Save_Restore_Scroll_Position
{
public partial class Form1 : Form
{
private DataTable _dataTable = new DataTable();
// スクロール位置を記憶するための変数
private int _savedVerticalRowIndex = -1;
private int _savedHorizontalOffset = 0;
public Form1()
{
InitializeComponent();
InitializeDataGridView();
btnSaveScroll.Click += btnSaveScroll_Click;
btnRestoreScroll.Click += btnRestoreScroll_Click;
}
private void InitializeDataGridView()
{
// テスト用に大量の列と行を作成(100行×20列)
for (int i = 1; i <= 20; i++)
{
_dataTable.Columns.Add($"Column_{i}", typeof(string));
}
for (int i = 1; i <= 100; i++)
{
DataRow row = _dataTable.NewRow();
for (int j = 1; j <= 20; j++)
{
row[$"Column_{j}"] = $"R{i}-C{j}";
}
_dataTable.Rows.Add(row);
}
dataGridView1.DataSource = _dataTable;
}
/// <summary>
/// 「位置を保存」ボタンのクリックイベント
/// </summary>
private void btnSaveScroll_Click(object? sender, EventArgs e)
{
// 1. 現在画面の最上部に表示されている行インデックスを保存
_savedVerticalRowIndex = dataGridView1.FirstDisplayedScrollingRowIndex;
// 2. 現在の水平スクロールのピクセルオフセット値を保存
_savedHorizontalOffset = dataGridView1.HorizontalScrollingOffset;
MessageBox.Show($"現在のスクロール位置を保存しました。\n垂直行: {_savedVerticalRowIndex}\n水平オフセット: {_savedHorizontalOffset}px");
}
/// <summary>
/// 「位置を復元」ボタンのクリックイベント
/// </summary>
private void btnRestoreScroll_Click(object? sender, EventArgs e)
{
// まだ一度も保存されていない場合は処理しない
if (_savedVerticalRowIndex == -1)
{
MessageBox.Show("先にスクロール位置を保存してください。");
return;
}
// --- 垂直スクロール(上下)の復元 ---
// 記憶した行インデックスが現時点の総行数を超えていないか安全確認を行う
if (_savedVerticalRowIndex >= 0 && _savedVerticalRowIndex < dataGridView1.RowCount)
{
try
{
dataGridView1.FirstDisplayedScrollingRowIndex = _savedVerticalRowIndex;
}
catch (InvalidOperationException)
{
// セルが非表示などの理由でスクロールできない場合の例外対策
}
}
// --- 水平スクロール(左右)の復元 ---
// 水平オフセットを代入して復元(範囲外の値が指定された場合は自動で最大値に丸められます)
if (_savedHorizontalOffset >= 0)
{
dataGridView1.HorizontalScrollingOffset = _savedHorizontalOffset;
}
}
}
}

サンプルアプリケーション

DataGridView_Save_Restore_Scroll_Position.zip
※ サンプルアプリケーションは、.NET 10 をターゲットに作成しています。

留意すべき注意点と実装の背景

スクロール位置の制御を例外エラーなく安定して動作させるため、実務で考慮すべきポイントが2つあります。

データ更新を跨ぐ際のインデックス範囲チェック

ボタンによる単純な保存・復元ではなく、「データの再読み込み処理」の前後でスクロール位置を維持する場合、保存時と復元時でデータ件数(総行数)が変わっている可能性があります。

たとえば、ユーザーが「50行目」が最上部に見える状態のときに保存ボタンを押し、その後データが削除されて全体で「30行」しかなくなったとします。この状態でチェックを行わずに FirstDisplayedScrollingRowIndex = 50; を実行すると、存在しない行を参照しようとしたため範囲外の例外発生(ArgumentOutOfRangeException)を招きます。

復元処理を呼び出す前には、必ずサンプルコードのように _savedVerticalRowIndex < dataGridView1.RowCount などの条件式を挟み、現在のグリッドの状態に対して妥当なインデックスであるかを確認する安全対策が必要です。

復元処理を実行するタイミング

データのバインド直後(dataGridView1.DataSource = _dataTable; を実行した直後の行)などに続けて復元処理を記述しても、位置が反映されないケースがあります。

これは、プログラムのコード上ではデータソースが割り当てられていても、Windows の内部システムが画面への実際の描画(レイアウト計算)を完了していないためです。

データの更新処理と同時にスクロール位置を復元したい場合は、更新コードの直後ではなく、DataGridView の描画の仕組みが一段落したタイミング(フォームの Activated イベントや、非同期処理の完了通知の後など)に処理を逃がすか、明示的に dataGridView1.Update() などを呼び出して描画を強制的に確定させてから復元プロパティに値を代入するアプローチが有効です。

まとめ

今回は、DataGridView のスクロール位置を保存し、任意のタイミングで復元する方法について解説しました。

垂直方向は行インデックス(FirstDisplayedScrollingRowIndex)、水平方向はピクセル単位(HorizontalScrollingOffset)という、管理単位の異なる2つのプロパティを組み合わせることで、表示位置の維持が実現できます。

  • 垂直位置の保存・復元には FirstDisplayedScrollingRowIndex を使用する
  • 水平位置の保存・復元には HorizontalScrollingOffset を使用する
  • 件数の変動によるエラーを防ぐため、復元前には必ず現在の総行数との範囲チェックを行う

データの検索や再読み込みが頻繁に行われる業務システムにおいて、スクロール位置を自動で維持する機能を組み込むことで、ユーザーに画面のブレを感じさせない洗練された操作環境を提供できるようになります。画面の更新ロジックを調整する際の参考になりましたら幸いです。


💬 コメント