Windows Forms DataGridView 컨트롤에서 Just-In-Time 데이터 로드를 사용하여 가상 모드 구현Implementing Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control

DataGridView 컨트롤에서 가상 모드를 구현 하는 한 가지 이유는 필요한 경우에만 데이터를 검색 하는 것입니다.One reason to implement virtual mode in the DataGridView control is to retrieve data only as it is needed. 이를 just-in-time 데이터 로드라고 합니다.This is called just-in-time data loading.

예를 들어 원격 데이터베이스에서 매우 큰 테이블로 작업 하는 경우 사용자가 새 행을 뷰로 스크롤하면 추가 데이터를 표시 하 고 검색 하는 데 필요한 데이터만 검색 하 여 시작 지연을 방지할 수 있습니다.If you are working with a very large table in a remote database, for example, you might want to avoid startup delays by retrieving only the data that is necessary for display and retrieving additional data only when the user scrolls new rows into view. 응용 프로그램을 실행 하는 클라이언트 컴퓨터에 데이터를 저장 하는 데 사용할 수 있는 제한 된 양의 메모리가 있는 경우 데이터베이스에서 새 값을 검색할 때 사용 하지 않는 데이터를 삭제할 수도 있습니다.If the client computers running your application have a limited amount of memory available for storing data, you might also want to discard unused data when retrieving new values from the database.

다음 섹션에서는 just-in-time 캐시로 DataGridView 컨트롤을 사용 하는 방법을 설명 합니다.The following sections describe how to use a DataGridView control with a just-in-time cache.

이 항목의 코드를 단일 목록으로 복사 하려면 방법: DataGridView 컨트롤 Windows Forms에서 Just-in-time 데이터 로드를 사용 하 여 가상 모드 구현을 참조 하세요.To copy the code in this topic as a single listing, see How to: Implement Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control.

양식The Form

다음 코드 예제에서는 CellValueNeeded 이벤트 처리기를 통해 Cache 개체와 상호 작용 하는 읽기 전용 DataGridView 컨트롤을 포함 하는 폼을 정의 합니다.The following code example defines a form containing a read-only DataGridView control that interacts with a Cache object through a CellValueNeeded event handler. Cache 개체는 로컬에 저장 된 값을 관리 하 고 DataRetriever 개체를 사용 하 여 sample Northwind 데이터베이스의 Orders 테이블에서 값을 검색 합니다.The Cache object manages the locally stored values and uses a DataRetriever object to retrieve values from the Orders table of the sample Northwind database. Cache 클래스에 필요한 IDataPageRetriever 인터페이스를 구현 하는 DataRetriever 개체는 DataGridView 컨트롤 행과 열을 초기화 하는 데에도 사용 됩니다.The DataRetriever object, which implements the IDataPageRetriever interface required by the Cache class, is also used to initialize the DataGridView control rows and columns.

IDataPageRetriever, DataRetrieverCache 유형은이 항목의 뒷부분에 설명 되어 있습니다.The IDataPageRetriever, DataRetriever, and Cache types are described later in this topic.

참고

암호와 같은 중요한 정보를 연결 문자열 내에 저장하면 애플리케이션 보안 문제가 발생할 수 있습니다.Storing sensitive information, such as a password, within the connection string can affect the security of your application. 데이터베이스 액세스를 제어할 경우에는 통합 보안이라고도 하는 Windows 인증을 사용하는 방법이 더 안전합니다.Using Windows Authentication (also known as integrated security) is a more secure way to control access to a database. 자세한 내용은 연결 정보 보호를 참조하세요.For more information, see Protecting Connection Information.

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 인터페이스The IDataPageRetriever Interface

다음 코드 예제에서는 DataRetriever 클래스에서 구현 하는 IDataPageRetriever 인터페이스를 정의 합니다.The following code example defines the IDataPageRetriever interface, which is implemented by the DataRetriever class. 이 인터페이스에 선언 된 유일한 메서드는 초기 행 인덱스와 데이터의 단일 페이지에 있는 행 수의 개수가 필요한 SupplyPageOfData 메서드입니다.The only method declared in this interface is the SupplyPageOfData method, which requires an initial row index and a count of the number of rows in a single page of data. 이러한 값은 구현자에서 데이터 원본에 있는 데이터의 하위 집합을 검색 하는 데 사용 됩니다.These values are used by the implementer to retrieve a subset of data from a data source.

Cache 개체는 생성 중에이 인터페이스의 구현을 사용 하 여 두 초기 데이터 페이지를 로드 합니다.A Cache object uses an implementation of this interface during construction to load two initial pages of data. 캐시 되지 않은 값이 필요할 때마다 캐시는 이러한 페이지 중 하나를 삭제 하 고 IDataPageRetriever의 값을 포함 하는 새 페이지를 요청 합니다.Whenever an uncached value is needed, the cache discards one of these pages and requests a new page containing the value from the 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 클래스The DataRetriever Class

다음 코드 예제에서는 IDataPageRetriever 인터페이스를 구현 하 여 서버에서 데이터 페이지를 검색 하는 DataRetriever 클래스를 정의 합니다.The following code example defines the DataRetriever class, which implements the IDataPageRetriever interface to retrieve pages of data from a server. DataRetriever 클래스는 또한 DataGridView 컨트롤이 필요한 열을 만들고 적절 한 수의 빈 행을 Rows 컬렉션에 추가 하는 데 사용 하는 ColumnsRowCount 속성을 제공 합니다.The DataRetriever class also provides Columns and RowCount properties, which the DataGridView control uses to create the necessary columns and to add the appropriate number of empty rows to the Rows collection. 컨트롤이 테이블의 모든 데이터를 포함 하는 것 처럼 동작 하 게 하려면 빈 행을 추가 해야 합니다.Adding the empty rows is necessary so that the control will behave as though it contains all the data in the table. 즉, 스크롤 막대의 스크롤 상자에 적절 한 크기가 포함 되 고 사용자가 테이블의 모든 행에 액세스할 수 있습니다.This means that the scroll box in the scroll bar will have the appropriate size, and the user will be able to access any row in the table. 행은 뷰로 스크롤할 때만 CellValueNeeded 이벤트 처리기에 의해 채워집니다.The rows are filled by the CellValueNeeded event handler only when they are scrolled into view.

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 클래스The Cache Class

다음 코드 예제에서는 IDataPageRetriever 구현을 통해 채워진 두 페이지의 데이터를 관리 하는 Cache 클래스를 정의 합니다.The following code example defines the Cache class, which manages two pages of data populated through an IDataPageRetriever implementation. Cache 클래스는 단일 캐시 페이지에 값을 저장 하 고 페이지의 위쪽 및 아래쪽 경계를 나타내는 행 인덱스를 계산 하는 DataTable를 포함 하는 내부 DataPage 구조를 정의 합니다.The Cache class defines an inner DataPage structure, which contains a DataTable to store the values in a single cache page and which calculates the row indexes that represent the upper and lower boundaries of the page.

Cache 클래스는 생성 시 두 개의 데이터 페이지를 로드 합니다.The Cache class loads two pages of data at construction time. CellValueNeeded 이벤트가 값을 요청할 때마다 Cache 개체는 해당 값을 두 페이지 중 하나에서 사용할 수 있는지 여부를 확인 하 고, 그럴 경우 반환 합니다.Whenever the CellValueNeeded event requests a value, the Cache object determines if the value is available in one of its two pages and, if so, returns it. 값을 로컬로 사용할 수 없는 경우 Cache 개체는 현재 표시 된 행에서 가장 멀리 있는 두 페이지를 확인 하 고이 페이지를 요청 된 값을 포함 하는 새 페이지로 대체 합니다. 그러면이 값이 반환 됩니다.If the value is not available locally, the Cache object determines which of its two pages is farthest from the currently displayed rows and replaces the page with a new one containing the requested value, which it then returns.

데이터 페이지의 행 수가 화면에 화면에 표시 될 수 있는 행 수와 동일 하다 고 가정할 경우이 모델을 사용 하면 사용자가 테이블을 페이징 하 여 가장 최근에 본 페이지로 효율적으로 돌아갈 수 있습니다.Assuming that the number of rows in a data page is the same as the number of rows that can be displayed on screen at once, this model allows users paging through the table to efficiently return to the most recently viewed page.

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

추가 고려 사항Additional Considerations

위의 코드 예제는 just-in-time 데이터 로드의 데모로 제공 됩니다.The previous code examples are provided as a demonstration of just-in-time data loading. 효율성을 극대화 하기 위해 고유한 요구 사항에 맞게 코드를 수정 해야 합니다.You will need to modify the code for your own needs to achieve maximum efficiency. 최소한 캐시에 있는 데이터 페이지당 행 수에 대 한 적절 한 값을 선택 해야 합니다.At minimum, you will need to choose an appropriate value for the number of rows per page of data in the cache. 이 값은 Cache 생성자에 전달 됩니다.This value is passed into the Cache constructor. 페이지당 행 수는 DataGridView 컨트롤에 동시에 표시할 수 있는 행 수보다 작아야 합니다.The number of rows per page should be no less than the number of rows that can be displayed simultaneously in your DataGridView control.

최상의 결과를 위해서는 시스템 및 사용자의 요구 사항을 확인 하기 위해 성능 테스트 및 유용성 테스트를 수행 해야 합니다.For best results, you will need to conduct performance testing and usability testing to determine the requirements of your system and your users. 고려해 야 할 몇 가지 요소에는 응용 프로그램을 실행 하는 클라이언트 컴퓨터의 메모리 양, 사용 되는 네트워크 연결의 사용 가능한 대역폭, 사용 되는 서버 대기 시간 등이 포함 됩니다.Several factors that you will need to take into consideration include the amount of memory in the client machines running your application, the available bandwidth of the network connection used, and the latency of the server used. 대역폭 및 대기 시간은 최대 사용량에 따라 결정 되어야 합니다.The bandwidth and latency should be determined at times of peak usage.

응용 프로그램의 스크롤 성능을 향상 시키기 위해 로컬에 저장 된 데이터의 양을 늘릴 수 있습니다.To improve the scrolling performance of your application, you can increase the amount of data stored locally. 그러나 시작 시간을 개선 하기 위해 처음에 너무 많은 데이터를 로드 하는 것을 방지 해야 합니다.To improve startup time, however, you must avoid loading too much data initially. Cache 클래스를 수정 하 여 저장할 수 있는 데이터 페이지 수를 늘릴 수 있습니다.You may want to modify the Cache class to increase the number of data pages it can store. 더 많은 데이터 페이지를 사용 하면 스크롤 효율성을 향상 시킬 수 있지만 사용 가능한 대역폭과 서버 대기 시간에 따라 데이터 페이지의 이상적인 행 수를 결정 해야 합니다.Using more data pages can improve scrolling efficiency, but you will need to determine the ideal number of rows in a data page, depending on the available bandwidth and the server latency. 페이지가 작을수록 서버는 더 자주 액세스 되지만 요청 된 데이터를 반환 하는 데 시간이 더 적게 소요 됩니다.With smaller pages, the server will be accessed more frequently, but will take less time to return the requested data. 대기 시간이 대역폭 보다 더 많은 문제인 경우 더 큰 데이터 페이지를 사용 하는 것이 좋습니다.If latency is more of an issue than bandwidth, you may want to use larger data pages.

참고 항목See also