在 NET 2.0 中缓存 WinForms DataGridView 中的数据






4.76/5 (13投票s)
2006年5月4日
3分钟阅读

161226

2420
一篇关于如何使用 DataGridView 的虚拟模式进行分页显示大量记录的文章。
引言
当您想在 DataGrid
中显示数百万条记录时,本文中描述的技术将非常有用。桌面应用程序的主要问题之一是,不像 Web 应用程序那样可以使用缓存。但在 Visual Studio 2005 中,Microsoft 在 DataGrid
中提供了非常好的支持,允许向用户显示数百万条数据记录。本文的灵感来自 MSDN 文章:在 Windows 窗体 DataGridView 控件中实现具有实时数据加载的虚拟模式。
背景
当您想向用户显示大量数据,并且不想一次性将所有数据加载到内存中时,可以使用本文背后的基本思想。这在 DataGridView
中是可行的,因为它具有虚拟模式,并且在虚拟模式下,您可以在每次重绘时编写每个单元格的值。因此,当您向下滚动 DataGridView
时,每个单元格将被绘制,并且为此,将调用 CellValueNeeded
事件。因此,虚拟模式下的 DataGridView
非常用户友好,因为您可以根据需要进行工作。
使用代码
基本上,此代码基于上面提到的 MSDN 文章。但是我让它对您来说更容易一些,现在我将向您解释它是如何实际工作的。
在这里,数据缓存背后的基本思想是,当需要单元格值时,您应该获取它并显示它。因此,屏幕背后发生的事情是
Interface -> IDataPageRetriver
Public Interface IDataPageRetriever
Function SupplyPageOfData( _
ByVal lowerPageBoundary As Integer, _
ByVal rowsPerPage As Integer) _
As DataTable
End Interface
此接口有一个 SupplyPageOfData
方法,该方法返回特定范围的 DataTable
。
类 - Cache
这是解决方案的核心。要理解这个基本思想,可以想象一本书中的一页,它有页码,以及上行号和下行号,这将像页面的上索引和下索引。因此,现在在页面中,您可以存储数据并记录。
在此类中,定义了一个结构 DataPage
。
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
结构成员是 DataTable
、下索引和上索引。创建此页面的对象时,DataTable
将分配给它,并设置下索引和上索引。
现在,当创建一个 Cache
类的对象时,它会创建两个默认页面。然后从数据库获取给定 PageSize
的 DataTable
并加载这两个页面。
Public Sub New(ByVal dataSupplier As IDataPageRetriever, _
ByVal rowsPerPage As Integer)
dataSupply = dataSupplier
Cache.RowsPerPage = rowsPerPage
LoadFirstTwoPages()
End Sub
并且当调用 CellValueNeeded
事件时,将调用具有行索引和列索引的 RetrieveElement
方法。
Private Sub grdFunctions_CellValueNeeded(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) _
Handles grdFunctions.CellValueNeeded
If blnStopRepaint Then
If e.RowIndex < dtRetrive.RowCount Then
e.Value = memoryCache.RetrieveElement(e.RowIndex, e.ColumnIndex)
End If
End If
End Sub
现在,此方法将检查数据是否已缓存。如果在缓存中,则返回这些值,否则它将从数据库中检索新数据并根据每个页面的下索引和上索引填充最接近的页面,即第零页或第一页。
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
UpdateCahnges()
Return RetrieveData_CacheIt_ThenReturnElement( _
rowIndex, columnIndex)
End If
End Function
现在,当您从网格更新或删除记录时,又出现了一个大问题。因为我们使用页面缓存和 CellValueNeeded
事件,所以我们需要在每次更新时更新单元格。因此,当用户更新任何单元格时,我会更新缓存 DataTable
。
Private Sub grdFunctions_CellValuePushed(ByVal sender _
As System.Object, ByVal e As _
System.Windows.Forms.DataGridViewCellValueEventArgs) _
Handles grdFunctions.CellValuePushed
memoryCache.SetRowElement(e.RowIndex, e.ColumnIndex, e.Value)
End Sub
Public Sub SetRowElement(ByVal rowIndex As Integer, _
ByVal colIndex As Integer, ByVal cellValue As String)
If IsRowCachedInPage(0, rowIndex) Then
cachePages(0).table.Rows(rowIndex _
Mod RowsPerPage).Item(colIndex) = cellValue
ElseIf IsRowCachedInPage(1, rowIndex) Then
cachePages(1).table.Rows(rowIndex _
Mod RowsPerPage).Item(colIndex) = cellValue
End If
End Sub
滚动时,当用户到达 Cache
类决定从数据库页面替换缓存页面时,我将获得更新的 DataSet
并引发事件,这将使网格用户更新数据库。
Private Sub UpdateCahnges()
Dim dtUpdate As DataTable = cachePages(0).table.GetChanges()
If Not dtUpdate Is Nothing AndAlso dtUpdate.Rows.Count > 0 Then
Dim _updateArgs As New UpdateDataArgs(dtUpdate)
RaiseEvent UpdateChangesToDB(Me, _updateArgs)
'MessageBox.Show(dtUpdate.Rows.Count.ToString & _
' " Rows are chaged in Page 0")
End If
dtUpdate = cachePages(1).table.GetChanges()
If Not dtUpdate Is Nothing AndAlso dtUpdate.Rows.Count > 0 Then
Dim _updateArgs As New UpdateDataArgs(dtUpdate)
RaiseEvent UpdateChangesToDB(Me, _updateArgs)
'MessageBox.Show(dtUpdate.Rows.Count.ToString & _
' " Rows are chaged in Page 1")
End If
End Sub
但是在删除操作的情况下,我们无法应用此方法,因此我们必须从数据库中删除记录并重新加载缓存页面。
Private Sub grdFunctions_UserDeletingRow(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.DataGridViewRowCancelEventArgs) _
Handles grdFunctions.UserDeletingRow
If blnStopRepaint Then
'Remove from the database first here
'DatabaseOP.DeleteRecord(connectionString, _
' grdFunctions.Rows(e.Row.Index).
' Cells(0).Value.ToString())
memoryCache.RemoveRow(e.Row.Index)
grdFunctions.InvalidateRow(e.Row.Index)
End If
End Sub
关注点
Microsoft 提供了一个非常好的控件,用于在 VS-2005 中使用 DataGridView
。
- MSDN.
历史
这是此 DataGrid
缓存解决方案的第一个版本,并且是使用 Oracle 数据库完成的,因为它有点难以使用。
在下一个版本中,我将致力于 DataGrid
控件,使其独立于任何数据库。我的意思是说,我将提供一个具有缓存解决方案的 DataGridView
控件,它将独立于数据库。