共用方式為


在 Windows Form DataGridView 控制項中以 Just-In-Time 資料載入方式實作虛擬模式

在 控制項中 DataGridView 實作虛擬模式的其中一個原因是只在需要時擷取資料。 這稱為 Just-In-Time 資料載入

例如,如果您在遠端資料庫中使用非常大的資料表,您可能會想要只擷取顯示和擷取其他資料所需的資料,以避免啟動延遲。只有當使用者將新的資料列捲動到檢視中時,才需要擷取其他資料。 如果執行應用程式的用戶端電腦有有限的記憶體可供儲存資料,您可能也想要在從資料庫擷取新值時捨棄未使用的資料。

下列各節說明如何搭配 Just-In-Time 快取使用 DataGridView 控制項。

若要將本主題中的程式碼複製為單一清單,請參閱 如何:在 Windows Forms DataGridView 控制項 中使用 Just-In-Time 資料載入實作虛擬模式。

表單

下列程式碼範例會定義表單,其中包含透過 CellValueNeeded 事件處理常式與 物件互動的 Cache 唯讀 DataGridView 控制項。 物件 Cache 會管理本機儲存的值,並使用 DataRetriever 物件從範例 Northwind 資料庫的 Orders 資料表擷取值。 物件 DataRetriever 會實 IDataPageRetriever 作 類別所需的 Cache 介面,也會用來初始化控制項資料 DataGridView 列和資料行。

本主題 IDataPageRetriever 稍後會說明 、 DataRetrieverCache 類型。

注意

在連接字串內儲存機密資訊 (例如密碼) 會影響應用程式的安全性。 使用 Windows 驗證 (也稱為整合式安全性) 是控制資料庫存取的更安全方式。 如需詳細資訊,請參閱保護連線資訊

using System;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Windows.Forms;

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());
    }
}
Imports System.Data
Imports System.Data.SqlClient
Imports System.Drawing
Imports System.Windows.Forms

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

IDataPageRetriever 介面

下列程式碼範例會 IDataPageRetriever 定義 類別所實作的 DataRetriever 介面。 這個介面中唯一宣告的方法是 SupplyPageOfData 方法,此方法需要初始資料列索引和單一資料頁中的資料列數目計數。 實作者會使用這些值,從資料來源擷取資料子集。

Cache物件會在建構期間使用這個介面的實作來載入兩個初始資料頁。 每當需要未快取的值時,快取就會捨棄其中一個頁面,並要求包含 值 IDataPageRetriever 的新頁面。

public interface IDataPageRetriever
{
    DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage);
}
Public Interface IDataPageRetriever

    Function SupplyPageOfData( _
        ByVal lowerPageBoundary As Integer, ByVal rowsPerPage As Integer) _
        As DataTable

End Interface

DataRetriever 類別

下列程式碼範例會 DataRetriever 定義 類別,其會實作 IDataPageRetriever 介面,以從伺服器擷取資料頁。 類別 DataRetriever 也提供 ColumnsRowCount 屬性, DataGridView 控制項會用來建立必要的資料行,並將適當的空白資料列數目新增至 Rows 集合。 新增空白資料列是必要的,讓控制項的行為就像它包含資料表中的所有資料一樣。 這表示捲軸中的捲動方塊會有適當的大小,而且使用者將能夠存取資料表中的任何資料列。 只有當資料列捲動到檢視中時, CellValueNeeded 事件處理常式才會填滿這些資料列。

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.
        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;
    }
}
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

快取類別

下列程式碼範例會定義 類別 Cache ,其會管理透過 IDataPageRetriever 實作填入的兩頁數據。 類別 Cache 會定義內部 DataPage 結構,其中包含 DataTable 將值儲存在單一快取頁面中的 ,並計算代表頁面上限和下限的資料列索引。

類別會在 Cache 建構時間載入兩頁的資料。 每當 CellValueNeeded 事件要求值時, Cache 物件就會判斷值是否在其兩個頁面的其中一個頁面中可用,如果是,則會傳回該值。 如果值在本機無法使用, Cache 物件會判斷其兩個頁面中哪一個離目前顯示的資料列最遠,並以包含所要求值的新頁面取代頁面,然後傳回該頁面。

假設資料頁中的資料列數目與一次在螢幕上顯示的資料列數目相同,此模型可讓使用者透過資料表分頁,有效率地返回最近檢視的頁面。

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;
    }
}
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

其他考量

先前的程式碼範例會以示範 Just-In-Time 資料載入的方式提供。 您必須修改自己的程式碼,才能達到最大效率。 您至少需要為快取中每個頁面的資料列數目選擇適當的值。 這個值會傳遞至建 Cache 構函式。 每個頁面的資料列數目應該不小於控制項中 DataGridView 可同時顯示的資料列數目。

為了獲得最佳結果,您必須進行效能測試和可用性測試,以判斷系統與使用者的需求。 您需要考慮的幾個因素包括執行應用程式的用戶端電腦記憶體數量、所使用的網路連線可用頻寬,以及所使用的伺服器延遲。 頻寬和延遲應在尖峰使用時決定。

若要改善應用程式的捲動效能,您可以增加儲存在本機的資料量。 不過,若要改善啟動時間,您一開始必須避免載入太多資料。 您可能想要修改 類別 Cache ,以增加可以儲存的資料頁數。 使用更多資料頁可以改善捲動效率,但您必須根據可用的頻寬和伺服器延遲,判斷資料頁中理想的資料列數目。 使用較小的頁面時,伺服器會更頻繁地存取,但需要較少的時間來傳回要求的資料。 如果延遲比頻寬更多,您可能想要使用較大的資料頁。

另請參閱