Windows フォーム DataGridView コントロールでの Just-In-Time データ読み込みによる仮想モードの実装
更新 : 2007 年 11 月
DataGridView コントロールで仮想モードを実装する 1 つの理由は、データを必要なときにのみ取得するためです。これを Just-In-Time データ読み込みと言います。
たとえば、リモート データベースの大型のテーブルを操作する場合、表示に必要なデータのみを取得し、ユーザーが新しい行をスクロール表示したときにのみ追加のデータを取得して、スタートアップの遅延を避ける必要があります。また、アプリケーションを実行するクライアント コンピュータで、データの保存に利用できるメモリの容量が制限されている場合には、データベースから新しい値を取得するときに不要なデータを破棄する必要もあります。
以下のセクションでは、Just-In-Time キャッシュで DataGridView コントロールを使用する方法について説明します。
このトピックのコードを単一のリストとしてコピーするには、「方法 : Windows フォーム DataGridView コントロールで Just-In-Time データ読み込みを使用して仮想モードを実装する」を参照してください。
フォーム
次のコード例では、CellValueNeeded イベント ハンドラ経由で Cache オブジェクトと対話する、読み取り専用の DataGridView コントロールを含むフォームを定義しています。Cache オブジェクトは、ローカルに保存された値を管理し、DataRetriever オブジェクトを使用して、Northwind サンプル データベースの Orders テーブルから値を取得します。また、Cache クラスで必要な IDataPageRetriever インターフェイスを実装する DataRetriever オブジェクトを使用して、DataGridView コントロールの行と列を初期化します。
IDataPageRetriever 型、DataRetriever 型、および Cache 型については、このトピックの後半で説明します。
メモ : |
---|
接続文字列内にパスワードなどの機密情報を格納すると、アプリケーションのセキュリティに影響を及ぼすことがあります。データベースへのアクセスを制御する方法としては、Windows 認証 (統合セキュリティとも呼ばれます) を使用する方が安全です。詳細については、「接続情報の保護 (ADO.NET)」を参照してください。 |
Public Class VirtualJustInTimeDemo
Inherits System.Windows.Forms.Form
Private WithEvents dataGridView1 As New DataGridView()
Private memoryCache As Cache
' Specify a connection string. Replace the given value with a
' valid connection string for a Northwind SQL Server sample
' database accessible to your system.
Private connectionString As String = _
"Initial Catalog=NorthWind;Data Source=localhost;" & _
"Integrated Security=SSPI;Persist Security Info=False"
Private table As String = "Orders"
Private Sub VirtualJustInTimeDemo_Load( _
ByVal sender As Object, ByVal e As EventArgs) _
Handles Me.Load
' Initialize the form.
With Me
.AutoSize = True
.Controls.Add(Me.dataGridView1)
.Text = "DataGridView virtual-mode just-in-time demo"
End With
' Complete the initialization of the DataGridView.
With Me.dataGridView1
.Size = New Size(800, 250)
.Dock = DockStyle.Fill
.VirtualMode = True
.ReadOnly = True
.AllowUserToAddRows = False
.AllowUserToOrderColumns = False
.SelectionMode = DataGridViewSelectionMode.FullRowSelect
End With
' Create a DataRetriever and use it to create a Cache object
' and to initialize the DataGridView columns and rows.
Try
Dim retriever As New DataRetriever(connectionString, table)
memoryCache = New Cache(retriever, 16)
For Each column As DataColumn In retriever.Columns
dataGridView1.Columns.Add( _
column.ColumnName, column.ColumnName)
Next
Me.dataGridView1.RowCount = retriever.RowCount
Catch ex As SqlException
MessageBox.Show("Connection could not be established. " & _
"Verify that the connection string is valid.")
Application.Exit()
End Try
' Adjust the column widths based on the displayed values.
Me.dataGridView1.AutoResizeColumns( _
DataGridViewAutoSizeColumnsMode.DisplayedCells)
End Sub
Private Sub dataGridView1_CellValueNeeded( _
ByVal sender As Object, ByVal e As DataGridViewCellValueEventArgs) _
Handles dataGridView1.CellValueNeeded
e.Value = memoryCache.RetrieveElement(e.RowIndex, e.ColumnIndex)
End Sub
<STAThreadAttribute()> _
Public Shared Sub Main()
Application.Run(New VirtualJustInTimeDemo())
End Sub
End Class
public class VirtualJustInTimeDemo : System.Windows.Forms.Form
{
private DataGridView dataGridView1 = new DataGridView();
private Cache memoryCache;
// Specify a connection string. Replace the given value with a
// valid connection string for a Northwind SQL Server sample
// database accessible to your system.
private string connectionString =
"Initial Catalog=NorthWind;Data Source=localhost;" +
"Integrated Security=SSPI;Persist Security Info=False";
private string table = "Orders";
protected override void OnLoad(EventArgs e)
{
// Initialize the form.
this.AutoSize = true;
this.Controls.Add(this.dataGridView1);
this.Text = "DataGridView virtual-mode just-in-time demo";
// Complete the initialization of the DataGridView.
this.dataGridView1.Size = new Size(800, 250);
this.dataGridView1.Dock = DockStyle.Fill;
this.dataGridView1.VirtualMode = true;
this.dataGridView1.ReadOnly = true;
this.dataGridView1.AllowUserToAddRows = false;
this.dataGridView1.AllowUserToOrderColumns = false;
this.dataGridView1.SelectionMode =
DataGridViewSelectionMode.FullRowSelect;
this.dataGridView1.CellValueNeeded += new
DataGridViewCellValueEventHandler(dataGridView1_CellValueNeeded);
// Create a DataRetriever and use it to create a Cache object
// and to initialize the DataGridView columns and rows.
try
{
DataRetriever retriever =
new DataRetriever(connectionString, table);
memoryCache = new Cache(retriever, 16);
foreach (DataColumn column in retriever.Columns)
{
dataGridView1.Columns.Add(
column.ColumnName, column.ColumnName);
}
this.dataGridView1.RowCount = retriever.RowCount;
}
catch (SqlException)
{
MessageBox.Show("Connection could not be established. " +
"Verify that the connection string is valid.");
Application.Exit();
}
// Adjust the column widths based on the displayed values.
this.dataGridView1.AutoResizeColumns(
DataGridViewAutoSizeColumnsMode.DisplayedCells);
base.OnLoad(e);
}
private void dataGridView1_CellValueNeeded(object sender,
DataGridViewCellValueEventArgs e)
{
e.Value = memoryCache.RetrieveElement(e.RowIndex, e.ColumnIndex);
}
[STAThreadAttribute()]
public static void Main()
{
Application.Run(new VirtualJustInTimeDemo());
}
}
IDataPageRetriever インターフェイス
DataRetriever クラスによって実装される IDataPageRetriever インターフェイスを定義するコード例を次に示します。このインターフェイスで宣言されているメソッドは、初期行インデックスと 1 ページのデータの行数を要求する SupplyPageOfData メソッドだけです。これらの値は、データ ソースからデータのサブセットを取得する実装側で使用されます。
Cache オブジェクトは、このインターフェイスの実装を構築時に使用して、データの最初の 2 ページを読み込みます。未キャッシュのデータが必要になると、キャッシュはこれらのページのいずれかを破棄し、IDataPageRetriever にその値を含む新しいページを要求します。
Public Interface IDataPageRetriever
Function SupplyPageOfData( _
ByVal lowerPageBoundary As Integer, ByVal rowsPerPage As Integer) _
As DataTable
End Interface
public interface IDataPageRetriever
{
DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage);
}
DataRetriever クラス
次に、DataRetriever クラスを定義するコード例を次に示します。このクラスは、データのページをサーバーから取得する IDataPageRetriever インターフェイスを実装します。DataRetriever クラスは、Columns プロパティと RowCount プロパティも提供します。DataGridView コントロールは、これらのプロパティを使用して、必要な列を作成し、適切な数の空の行を Rows コレクションに追加します。空の行の追加は、テーブル内のデータがすべて存在するようにコントロールが動作するうえで必要です。これにより、スクロール バーのスクロール ボックスが適切なサイズになり、ユーザーがテーブル内の任意の行にアクセスできるようになります。行は、スクロール表示されたときにのみ、CellValueNeeded イベント ハンドラによって値が設定されます。
Public Class DataRetriever
Implements IDataPageRetriever
Private tableName As String
Private command As SqlCommand
Public Sub New( _
ByVal connectionString As String, ByVal tableName As String)
Dim connection As New SqlConnection(connectionString)
connection.Open()
command = connection.CreateCommand()
Me.tableName = tableName
End Sub
Private rowCountValue As Integer = -1
Public ReadOnly Property RowCount() As Integer
Get
' Return the existing value if it has already been determined.
If Not rowCountValue = -1 Then
Return rowCountValue
End If
' Retrieve the row count from the database.
command.CommandText = "SELECT COUNT(*) FROM " & tableName
rowCountValue = CInt(command.ExecuteScalar())
Return rowCountValue
End Get
End Property
Private columnsValue As DataColumnCollection
Public ReadOnly Property Columns() As DataColumnCollection
Get
' Return the existing value if it has already been determined.
If columnsValue IsNot Nothing Then
Return columnsValue
End If
' Retrieve the column information from the database.
command.CommandText = "SELECT * FROM " & tableName
Dim adapter As New SqlDataAdapter()
adapter.SelectCommand = command
Dim table As New DataTable()
table.Locale = System.Globalization.CultureInfo.InvariantCulture
adapter.FillSchema(table, SchemaType.Source)
columnsValue = table.Columns
Return columnsValue
End Get
End Property
Private commaSeparatedListOfColumnNamesValue As String = Nothing
Private ReadOnly Property CommaSeparatedListOfColumnNames() As String
Get
' Return the existing value if it has already been determined.
If commaSeparatedListOfColumnNamesValue IsNot Nothing Then
Return commaSeparatedListOfColumnNamesValue
End If
' Store a list of column names for use in the
' SupplyPageOfData method.
Dim commaSeparatedColumnNames As New System.Text.StringBuilder()
Dim firstColumn As Boolean = True
For Each column As DataColumn In Columns
If Not firstColumn Then
commaSeparatedColumnNames.Append(", ")
End If
commaSeparatedColumnNames.Append(column.ColumnName)
firstColumn = False
Next
commaSeparatedListOfColumnNamesValue = _
commaSeparatedColumnNames.ToString()
Return commaSeparatedListOfColumnNamesValue
End Get
End Property
' Declare variables to be reused by the SupplyPageOfData method.
Private columnToSortBy As String
Private adapter As New SqlDataAdapter()
Public Function SupplyPageOfData( _
ByVal lowerPageBoundary As Integer, ByVal rowsPerPage As Integer) _
As DataTable Implements IDataPageRetriever.SupplyPageOfData
' Store the name of the ID column. This column must contain unique
' values so the SQL below will work properly.
If columnToSortBy Is Nothing Then
columnToSortBy = Me.Columns(0).ColumnName
End If
If Not Me.Columns(columnToSortBy).Unique Then
Throw New InvalidOperationException(String.Format( _
"Column {0} must contain unique values.", columnToSortBy))
End If
' Retrieve the specified number of rows from the database, starting
' with the row specified by the lowerPageBoundary parameter.
command.CommandText = _
"Select Top " & rowsPerPage & " " & _
CommaSeparatedListOfColumnNames & " From " & tableName & _
" WHERE " & columnToSortBy & " NOT IN (SELECT TOP " & _
lowerPageBoundary & " " & columnToSortBy & " From " & _
tableName & " Order By " & columnToSortBy & _
") Order By " & columnToSortBy
adapter.SelectCommand = command
Dim table As New DataTable()
table.Locale = System.Globalization.CultureInfo.InvariantCulture
adapter.Fill(table)
Return table
End Function
End Class
public class DataRetriever : IDataPageRetriever
{
private string tableName;
private SqlCommand command;
public DataRetriever(string connectionString, string tableName)
{
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
command = connection.CreateCommand();
this.tableName = tableName;
}
private int rowCountValue = -1;
public int RowCount
{
get
{
// Return the existing value if it has already been determined.
if (rowCountValue != -1)
{
return rowCountValue;
}
// Retrieve the row count from the database.
command.CommandText = "SELECT COUNT(*) FROM " + tableName;
rowCountValue = (int)command.ExecuteScalar();
return rowCountValue;
}
}
private DataColumnCollection columnsValue;
public DataColumnCollection Columns
{
get
{
// Return the existing value if it has already been determined.
if (columnsValue != null)
{
return columnsValue;
}
// Retrieve the column information from the database.
command.CommandText = "SELECT * FROM " + tableName;
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.FillSchema(table, SchemaType.Source);
columnsValue = table.Columns;
return columnsValue;
}
}
private string commaSeparatedListOfColumnNamesValue = null;
private string CommaSeparatedListOfColumnNames
{
get
{
// Return the existing value if it has already been determined.
if (commaSeparatedListOfColumnNamesValue != null)
{
return commaSeparatedListOfColumnNamesValue;
}
// Store a list of column names for use in the
// SupplyPageOfData method.
System.Text.StringBuilder commaSeparatedColumnNames =
new System.Text.StringBuilder();
bool firstColumn = true;
foreach (DataColumn column in Columns)
{
if (!firstColumn)
{
commaSeparatedColumnNames.Append(", ");
}
commaSeparatedColumnNames.Append(column.ColumnName);
firstColumn = false;
}
commaSeparatedListOfColumnNamesValue =
commaSeparatedColumnNames.ToString();
return commaSeparatedListOfColumnNamesValue;
}
}
// Declare variables to be reused by the SupplyPageOfData method.
private string columnToSortBy;
private SqlDataAdapter adapter = new SqlDataAdapter();
public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
// Store the name of the ID column. This column must contain unique
// values so the SQL below will work properly.
if (columnToSortBy == null)
{
columnToSortBy = this.Columns[0].ColumnName;
}
if (!this.Columns[columnToSortBy].Unique)
{
throw new InvalidOperationException(String.Format(
"Column {0} must contain unique values.", columnToSortBy));
}
// Retrieve the specified number of rows from the database, starting
// with the row specified by the lowerPageBoundary parameter.
command.CommandText = "Select Top " + rowsPerPage + " " +
CommaSeparatedListOfColumnNames + " From " + tableName +
" WHERE " + columnToSortBy + " NOT IN (SELECT TOP " +
lowerPageBoundary + " " + columnToSortBy + " From " +
tableName + " Order By " + columnToSortBy +
") Order By " + columnToSortBy;
adapter.SelectCommand = command;
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.Fill(table);
return table;
}
}
Cache クラス
次に、Cache クラスを定義するコード例を示します。このクラスは、IDataPageRetriever 実装によって設定される 2 ページのデータを管理します。Cache クラスは、内側の DataPage 構造体を定義します。この構造体は、値を 1 つのキャッシュ ページに保存する DataTable を含み、ページの上下の境界を表す行インデックスを計算します。
Cache クラスは、構築時に 2 ページのデータを読み込みます。CellValueNeeded イベントが値を要求すると、Cache オブジェクトは、その 2 ページのいずれかに値が存在するかどうか確認し、値が存在する場合には返します。値がローカルに存在しない場合、Cache は、2 つのページのうちで、現在表示されている行から遠い方のページを特定し、そのページを、要求されている値を含む新しいページと置き換えて、値を返します。
データ ページの行数と、画面に同時に表示できる行数が一致することを前提に、このモデルでは、テーブルをページングしているユーザーが、直前に表示されたページに効率的に復帰できます。
Public Class Cache
Private Shared RowsPerPage As Integer
' Represents one page of data.
Public Structure DataPage
Public table As DataTable
Private lowestIndexValue As Integer
Private highestIndexValue As Integer
Public Sub New(ByVal table As DataTable, ByVal rowIndex As Integer)
Me.table = table
lowestIndexValue = MapToLowerBoundary(rowIndex)
highestIndexValue = MapToUpperBoundary(rowIndex)
System.Diagnostics.Debug.Assert(lowestIndexValue >= 0)
System.Diagnostics.Debug.Assert(highestIndexValue >= 0)
End Sub
Public ReadOnly Property LowestIndex() As Integer
Get
Return lowestIndexValue
End Get
End Property
Public ReadOnly Property HighestIndex() As Integer
Get
Return highestIndexValue
End Get
End Property
Public Shared Function MapToLowerBoundary( _
ByVal rowIndex As Integer) As Integer
' Return the lowest index of a page containing the given index.
Return (rowIndex \ RowsPerPage) * RowsPerPage
End Function
Private Shared Function MapToUpperBoundary( _
ByVal rowIndex As Integer) As Integer
' Return the highest index of a page containing the given index.
Return MapToLowerBoundary(rowIndex) + RowsPerPage - 1
End Function
End Structure
Private cachePages As DataPage()
Private dataSupply As IDataPageRetriever
Public Sub New(ByVal dataSupplier As IDataPageRetriever, _
ByVal rowsPerPage As Integer)
dataSupply = dataSupplier
Cache.RowsPerPage = rowsPerPage
LoadFirstTwoPages()
End Sub
' Sets the value of the element parameter if the value is in the cache.
Private Function IfPageCached_ThenSetElement(ByVal rowIndex As Integer, _
ByVal columnIndex As Integer, ByRef element As String) As Boolean
If IsRowCachedInPage(0, rowIndex) Then
element = cachePages(0).table.Rows(rowIndex Mod RowsPerPage) _
.Item(columnIndex).ToString()
Return True
ElseIf IsRowCachedInPage(1, rowIndex) Then
element = cachePages(1).table.Rows(rowIndex Mod RowsPerPage) _
.Item(columnIndex).ToString()
Return True
End If
Return False
End Function
Public Function RetrieveElement(ByVal rowIndex As Integer, _
ByVal columnIndex As Integer) As String
Dim element As String = Nothing
If IfPageCached_ThenSetElement(rowIndex, columnIndex, element) Then
Return element
Else
Return RetrieveData_CacheIt_ThenReturnElement( _
rowIndex, columnIndex)
End If
End Function
Private Sub LoadFirstTwoPages()
cachePages = New DataPage() { _
New DataPage(dataSupply.SupplyPageOfData( _
DataPage.MapToLowerBoundary(0), RowsPerPage), 0), _
New DataPage(dataSupply.SupplyPageOfData( _
DataPage.MapToLowerBoundary(RowsPerPage), _
RowsPerPage), RowsPerPage) _
}
End Sub
Private Function RetrieveData_CacheIt_ThenReturnElement( _
ByVal rowIndex As Integer, ByVal columnIndex As Integer) As String
' Retrieve a page worth of data containing the requested value.
Dim table As DataTable = dataSupply.SupplyPageOfData( _
DataPage.MapToLowerBoundary(rowIndex), RowsPerPage)
' Replace the cached page furthest from the requested cell
' with a new page containing the newly retrieved data.
cachePages(GetIndexToUnusedPage(rowIndex)) = _
New DataPage(table, rowIndex)
Return RetrieveElement(rowIndex, columnIndex)
End Function
' Returns the index of the cached page most distant from the given index
' and therefore least likely to be reused.
Private Function GetIndexToUnusedPage(ByVal rowIndex As Integer) _
As Integer
If rowIndex > cachePages(0).HighestIndex AndAlso _
rowIndex > cachePages(1).HighestIndex Then
Dim offsetFromPage0 As Integer = _
rowIndex - cachePages(0).HighestIndex
Dim offsetFromPage1 As Integer = _
rowIndex - cachePages(1).HighestIndex
If offsetFromPage0 < offsetFromPage1 Then
Return 1
End If
Return 0
Else
Dim offsetFromPage0 As Integer = _
cachePages(0).LowestIndex - rowIndex
Dim offsetFromPage1 As Integer = _
cachePages(1).LowestIndex - rowIndex
If offsetFromPage0 < offsetFromPage1 Then
Return 1
End If
Return 0
End If
End Function
' Returns a value indicating whether the given row index is contained
' in the given DataPage.
Private Function IsRowCachedInPage( _
ByVal pageNumber As Integer, ByVal rowIndex As Integer) As Boolean
Return rowIndex <= cachePages(pageNumber).HighestIndex AndAlso _
rowIndex >= cachePages(pageNumber).LowestIndex
End Function
End Class
public class Cache
{
private static int RowsPerPage;
// Represents one page of data.
public struct DataPage
{
public DataTable table;
private int lowestIndexValue;
private int highestIndexValue;
public DataPage(DataTable table, int rowIndex)
{
this.table = table;
lowestIndexValue = MapToLowerBoundary(rowIndex);
highestIndexValue = MapToUpperBoundary(rowIndex);
System.Diagnostics.Debug.Assert(lowestIndexValue >= 0);
System.Diagnostics.Debug.Assert(highestIndexValue >= 0);
}
public int LowestIndex
{
get
{
return lowestIndexValue;
}
}
public int HighestIndex
{
get
{
return highestIndexValue;
}
}
public static int MapToLowerBoundary(int rowIndex)
{
// Return the lowest index of a page containing the given index.
return (rowIndex / RowsPerPage) * RowsPerPage;
}
private static int MapToUpperBoundary(int rowIndex)
{
// Return the highest index of a page containing the given index.
return MapToLowerBoundary(rowIndex) + RowsPerPage - 1;
}
}
private DataPage[] cachePages;
private IDataPageRetriever dataSupply;
public Cache(IDataPageRetriever dataSupplier, int rowsPerPage)
{
dataSupply = dataSupplier;
Cache.RowsPerPage = rowsPerPage;
LoadFirstTwoPages();
}
// Sets the value of the element parameter if the value is in the cache.
private bool IfPageCached_ThenSetElement(int rowIndex,
int columnIndex, ref string element)
{
if (IsRowCachedInPage(0, rowIndex))
{
element = cachePages[0].table
.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
return true;
}
else if (IsRowCachedInPage(1, rowIndex))
{
element = cachePages[1].table
.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
return true;
}
return false;
}
public string RetrieveElement(int rowIndex, int columnIndex)
{
string element = null;
if (IfPageCached_ThenSetElement(rowIndex, columnIndex, ref element))
{
return element;
}
else
{
return RetrieveData_CacheIt_ThenReturnElement(
rowIndex, columnIndex);
}
}
private void LoadFirstTwoPages()
{
cachePages = new DataPage[]{
new DataPage(dataSupply.SupplyPageOfData(
DataPage.MapToLowerBoundary(0), RowsPerPage), 0),
new DataPage(dataSupply.SupplyPageOfData(
DataPage.MapToLowerBoundary(RowsPerPage),
RowsPerPage), RowsPerPage)};
}
private string RetrieveData_CacheIt_ThenReturnElement(
int rowIndex, int columnIndex)
{
// Retrieve a page worth of data containing the requested value.
DataTable table = dataSupply.SupplyPageOfData(
DataPage.MapToLowerBoundary(rowIndex), RowsPerPage);
// Replace the cached page furthest from the requested cell
// with a new page containing the newly retrieved data.
cachePages[GetIndexToUnusedPage(rowIndex)] = new DataPage(table, rowIndex);
return RetrieveElement(rowIndex, columnIndex);
}
// Returns the index of the cached page most distant from the given index
// and therefore least likely to be reused.
private int GetIndexToUnusedPage(int rowIndex)
{
if (rowIndex > cachePages[0].HighestIndex &&
rowIndex > cachePages[1].HighestIndex)
{
int offsetFromPage0 = rowIndex - cachePages[0].HighestIndex;
int offsetFromPage1 = rowIndex - cachePages[1].HighestIndex;
if (offsetFromPage0 < offsetFromPage1)
{
return 1;
}
return 0;
}
else
{
int offsetFromPage0 = cachePages[0].LowestIndex - rowIndex;
int offsetFromPage1 = cachePages[1].LowestIndex - rowIndex;
if (offsetFromPage0 < offsetFromPage1)
{
return 1;
}
return 0;
}
}
// Returns a value indicating whether the given row index is contained
// in the given DataPage.
private bool IsRowCachedInPage(int pageNumber, int rowIndex)
{
return rowIndex <= cachePages[pageNumber].HighestIndex &&
rowIndex >= cachePages[pageNumber].LowestIndex;
}
}
その他の考慮事項
上記のコード例は、Just-In-Time データ読み込みの例として紹介されています。そのため、最大の効率を確保するには、独自のニーズに合わせてコードを変更する必要があります。少なくとも、キャッシュ内の 1 ページあたりのデータの行数として適切な値を選択する必要があります。この値は、Cache コンストラクタに渡されます。1 ページあたりの行数は、DataGridView コントロールに同時に表示できる行数以上にする必要があります。
最適な結果を得るには、パフォーマンス テストと操作性テストを実行し、システムとユーザーの要件を確認する必要があります。考慮する必要がある要素には、アプリケーションを実行するクライアント コンピュータのメモリ容量、使用するネットワーク接続の有効帯域幅、使用するサーバーの待機時間などがあります。帯域幅と待機時間は、ピーク使用時に測定する必要があります。
アプリケーションのスクロール性能を向上させるには、ローカルに保存されるデータ量を増やします。ただし、起動時間を短縮するには、最初に過度のデータを読み込まないようにする必要があります。Cache クラスを修正して、保存できるデータ ページ数を増大することもできます。データ ページをより多く使用すると、スクロール効率を改善できますが、有効な帯域幅とサーバーの待機時間に応じて、データ ページの適切な行数を決める必要があります。ページ数を少なくすると、サーバーへのアクセス頻度が増大しますが、要求したデータが返される時間が短縮します。帯域幅よりも待機時間の方が問題になる場合は、データ ページを増やす必要があります。
参照
処理手順
チュートリアル : Windows フォーム DataGridView コントロールでの仮想モードの実装
方法 : Windows フォーム DataGridView コントロールで Just-In-Time データ読み込みを使用して仮想モードを実装する
概念
Windows フォーム DataGridView コントロールを拡張するための推奨される手順
Windows フォーム DataGridView コントロールでの仮想モード