演练:在 Windows 窗体 DataGridView 控件中实现虚拟模式
更新:2007 年 11 月
如果要在 DataGridView 控件中显示非常大量的表格数据,可以将 VirtualMode 属性设置为 true,并显式管理控件与其数据存储区的交互。这样,您便可以在这种情况下精细调整该控件的性能。
DataGridView 控件提供若干事件,您可以通过处理这些事件来与自定义数据存储区进行交互。本演练引导您完成实现这些事件处理程序的过程。为便于说明,本主题中的代码示例使用一个非常简单的数据源。在生产设置中,通常仅将需要显示的行加载到缓存中,并处理 DataGridView 事件以便与缓存进行交互和更新缓存。有关更多信息,请参见 在 Windows 窗体 DataGridView 控件中实现实时数据加载的虚拟模式
若要以单个列表的形式复制本主题中的代码,请参见 如何:在 Windows 窗体 DataGridView 控件中实现虚拟模式。
创建窗体
实现虚拟模式
创建一个从 Form 派生并包含 DataGridView 控件的类。
下面的代码包含一些简单的初始化操作。它将声明一些将在后续步骤中使用的变量,提供 Main 方法,并在类的构造函数中提供一个简单的窗体布局。
Imports System Imports System.Windows.Forms Public Class Form1 Inherits Form Private WithEvents dataGridView1 As New DataGridView() ' Declare an ArrayList to serve as the data store. Private customers As New System.Collections.ArrayList() ' Declare a Customer object to store data for a row being edited. Private customerInEdit As Customer ' Declare a variable to store the index of a row being edited. ' A value of -1 indicates that there is no row currently in edit. Private rowInEdit As Integer = -1 ' Declare a variable to indicate the commit scope. ' Set this value to false to use cell-level commit scope. Private rowScopeCommit As Boolean = True <STAThreadAttribute()> _ Public Shared Sub Main() Application.Run(New Form1()) End Sub Public Sub New() ' Initialize the form. Me.dataGridView1.Dock = DockStyle.Fill Me.Controls.Add(Me.dataGridView1) Me.Text = "DataGridView virtual-mode demo (row-level commit scope)" End Sub ... End Class
using System; using System.Windows.Forms; public class Form1 : Form { private DataGridView dataGridView1 = new DataGridView(); // Declare an ArrayList to serve as the data store. private System.Collections.ArrayList customers = new System.Collections.ArrayList(); // Declare a Customer object to store data for a row being edited. private Customer customerInEdit; // Declare a variable to store the index of a row being edited. // A value of -1 indicates that there is no row currently in edit. private int rowInEdit = -1; // Declare a variable to indicate the commit scope. // Set this value to false to use cell-level commit scope. private bool rowScopeCommit = true; [STAThreadAttribute()] public static void Main() { Application.Run(new Form1()); } public Form1() { // Initialize the form. this.dataGridView1.Dock = DockStyle.Fill; this.Controls.Add(this.dataGridView1); this.Load += new EventHandler(Form1_Load); this.Text = "DataGridView virtual-mode demo (row-level commit scope)"; } ... }
为窗体的 Load 事件实现一个处理程序,该处理程序初始化 DataGridView 控件并用示例值填充数据存储区。
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles Me.Load ' Enable virtual mode. Me.dataGridView1.VirtualMode = True ' Add columns to the DataGridView. Dim companyNameColumn As New DataGridViewTextBoxColumn() With companyNameColumn .HeaderText = "Company Name" .Name = "Company Name" End With Dim contactNameColumn As New DataGridViewTextBoxColumn() With contactNameColumn .HeaderText = "Contact Name" .Name = "Contact Name" End With Me.dataGridView1.Columns.Add(companyNameColumn) Me.dataGridView1.Columns.Add(contactNameColumn) Me.dataGridView1.AutoSizeColumnsMode = _ DataGridViewAutoSizeColumnsMode.AllCells ' Add some sample entries to the data store. Me.customers.Add(New Customer("Bon app'", "Laurence Lebihan")) Me.customers.Add(New Customer("Bottom-Dollar Markets", _ "Elizabeth Lincoln")) Me.customers.Add(New Customer("B's Beverages", "Victoria Ashworth")) ' Set the row count, including the row for new records. Me.dataGridView1.RowCount = 4 End Sub
private void Form1_Load(object sender, EventArgs e) { // Enable virtual mode. this.dataGridView1.VirtualMode = true; // Connect the virtual-mode events to event handlers. this.dataGridView1.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView1_CellValueNeeded); this.dataGridView1.CellValuePushed += new DataGridViewCellValueEventHandler(dataGridView1_CellValuePushed); this.dataGridView1.NewRowNeeded += new DataGridViewRowEventHandler(dataGridView1_NewRowNeeded); this.dataGridView1.RowValidated += new DataGridViewCellEventHandler(dataGridView1_RowValidated); this.dataGridView1.RowDirtyStateNeeded += new QuestionEventHandler(dataGridView1_RowDirtyStateNeeded); this.dataGridView1.CancelRowEdit += new QuestionEventHandler(dataGridView1_CancelRowEdit); this.dataGridView1.UserDeletingRow += new DataGridViewRowCancelEventHandler(dataGridView1_UserDeletingRow); // Add columns to the DataGridView. DataGridViewTextBoxColumn companyNameColumn = new DataGridViewTextBoxColumn(); companyNameColumn.HeaderText = "Company Name"; companyNameColumn.Name = "Company Name"; DataGridViewTextBoxColumn contactNameColumn = new DataGridViewTextBoxColumn(); contactNameColumn.HeaderText = "Contact Name"; contactNameColumn.Name = "Contact Name"; this.dataGridView1.Columns.Add(companyNameColumn); this.dataGridView1.Columns.Add(contactNameColumn); this.dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells; // Add some sample entries to the data store. this.customers.Add(new Customer( "Bon app'", "Laurence Lebihan")); this.customers.Add(new Customer( "Bottom-Dollar Markets", "Elizabeth Lincoln")); this.customers.Add(new Customer( "B's Beverages", "Victoria Ashworth")); // Set the row count, including the row for new records. this.dataGridView1.RowCount = 4; }
为 CellValueNeeded 事件实现一个处理程序,从数据存储区或当前正在编辑的 Customer 对象中检索请求的单元格值。
只要 DataGridView 控件需要绘制单元格,就会发生此事件。
Private Sub dataGridView1_CellValueNeeded(ByVal sender As Object, _ ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) _ Handles dataGridView1.CellValueNeeded ' If this is the row for new records, no values are needed. If e.RowIndex = Me.dataGridView1.RowCount - 1 Then Return End If Dim customerTmp As Customer = Nothing ' Store a reference to the Customer object for the row being painted. If e.RowIndex = rowInEdit Then customerTmp = Me.customerInEdit Else customerTmp = CType(Me.customers(e.RowIndex), Customer) End If ' Set the cell value to paint using the Customer object retrieved. Select Case Me.dataGridView1.Columns(e.ColumnIndex).Name Case "Company Name" e.Value = customerTmp.CompanyName Case "Contact Name" e.Value = customerTmp.ContactName End Select End Sub
private void dataGridView1_CellValueNeeded(object sender, System.Windows.Forms.DataGridViewCellValueEventArgs e) { // If this is the row for new records, no values are needed. if (e.RowIndex == this.dataGridView1.RowCount - 1) return; Customer customerTmp = null; // Store a reference to the Customer object for the row being painted. if (e.RowIndex == rowInEdit) { customerTmp = this.customerInEdit; } else { customerTmp = (Customer)this.customers[e.RowIndex]; } // Set the cell value to paint using the Customer object retrieved. switch (this.dataGridView1.Columns[e.ColumnIndex].Name) { case "Company Name": e.Value = customerTmp.CompanyName; break; case "Contact Name": e.Value = customerTmp.ContactName; break; } }
为 CellValuePushed 事件实现一个处理程序,将编辑后的单元格值存储在代表已编辑的行的 Customer 对象中。只要用户提交对单元格值的更改,就会发生此事件。
Private Sub dataGridView1_CellValuePushed(ByVal sender As Object, _ ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) _ Handles dataGridView1.CellValuePushed Dim customerTmp As Customer = Nothing ' Store a reference to the Customer object for the row being edited. If e.RowIndex < Me.customers.Count Then ' If the user is editing a new row, create a new Customer object. If Me.customerInEdit Is Nothing Then Me.customerInEdit = New Customer( _ CType(Me.customers(e.RowIndex), Customer).CompanyName, _ CType(Me.customers(e.RowIndex), Customer).ContactName) End If customerTmp = Me.customerInEdit Me.rowInEdit = e.RowIndex Else customerTmp = Me.customerInEdit End If ' Set the appropriate Customer property to the cell value entered. Dim newValue As String = TryCast(e.Value, String) Select Case Me.dataGridView1.Columns(e.ColumnIndex).Name Case "Company Name" customerTmp.CompanyName = newValue Case "Contact Name" customerTmp.ContactName = newValue End Select End Sub
private void dataGridView1_CellValuePushed(object sender, System.Windows.Forms.DataGridViewCellValueEventArgs e) { Customer customerTmp = null; // Store a reference to the Customer object for the row being edited. if (e.RowIndex < this.customers.Count) { // If the user is editing a new row, create a new Customer object. if (this.customerInEdit == null) { this.customerInEdit = new Customer( ((Customer)this.customers[e.RowIndex]).CompanyName, ((Customer)this.customers[e.RowIndex]).ContactName); } customerTmp = this.customerInEdit; this.rowInEdit = e.RowIndex; } else { customerTmp = this.customerInEdit; } // Set the appropriate Customer property to the cell value entered. String newValue = e.Value as String; switch (this.dataGridView1.Columns[e.ColumnIndex].Name) { case "Company Name": customerTmp.CompanyName = newValue; break; case "Contact Name": customerTmp.ContactName = newValue; break; } }
为 NewRowNeeded 事件实现一个处理程序,创建一个代表新建行的新 Customer 对象。
只要用户进入新的记录行,就会发生此事件。
Private Sub dataGridView1_NewRowNeeded(ByVal sender As Object, _ ByVal e As System.Windows.Forms.DataGridViewRowEventArgs) _ Handles dataGridView1.NewRowNeeded ' Create a new Customer object when the user edits ' the row for new records. Me.customerInEdit = New Customer() Me.rowInEdit = Me.dataGridView1.Rows.Count - 1 End Sub
private void dataGridView1_NewRowNeeded(object sender, System.Windows.Forms.DataGridViewRowEventArgs e) { // Create a new Customer object when the user edits // the row for new records. this.customerInEdit = new Customer(); this.rowInEdit = this.dataGridView1.Rows.Count - 1; }
为 RowValidated 事件实现一个处理程序,将新的或修改后的行保存到数据存储区中。
只要用户更改当前行,就会发生此事件。
Private Sub dataGridView1_RowValidated(ByVal sender As Object, _ ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) _ Handles dataGridView1.RowValidated ' Save row changes if any were made and release the edited ' Customer object if there is one. If e.RowIndex >= Me.customers.Count AndAlso _ e.RowIndex <> Me.dataGridView1.Rows.Count - 1 Then ' Add the new Customer object to the data store. Me.customers.Add(Me.customerInEdit) Me.customerInEdit = Nothing Me.rowInEdit = -1 ElseIf (Me.customerInEdit IsNot Nothing) AndAlso _ e.RowIndex < Me.customers.Count Then ' Save the modified Customer object in the data store. Me.customers(e.RowIndex) = Me.customerInEdit Me.customerInEdit = Nothing Me.rowInEdit = -1 ElseIf Me.dataGridView1.ContainsFocus Then Me.customerInEdit = Nothing Me.rowInEdit = -1 End If End Sub
private void dataGridView1_RowValidated(object sender, System.Windows.Forms.DataGridViewCellEventArgs e) { // Save row changes if any were made and release the edited // Customer object if there is one. if (e.RowIndex >= this.customers.Count && e.RowIndex != this.dataGridView1.Rows.Count - 1) { // Add the new Customer object to the data store. this.customers.Add(this.customerInEdit); this.customerInEdit = null; this.rowInEdit = -1; } else if (this.customerInEdit != null && e.RowIndex < this.customers.Count) { // Save the modified Customer object in the data store. this.customers[e.RowIndex] = this.customerInEdit; this.customerInEdit = null; this.rowInEdit = -1; } else if (this.dataGridView1.ContainsFocus) { this.customerInEdit = null; this.rowInEdit = -1; } }
为 RowDirtyStateNeeded 事件实现一个处理程序,指示当用户通过在编辑模式下按 Esc 两次或在非编辑模式下按 Esc 一次来指示行恢复时,是否发生 CancelRowEdit 事件。
默认情况下,除非 RowDirtyStateNeeded 事件处理程序中的 QuestionEventArgs.Response 属性设置为 true,否则,只要当前行中有任何单元格被修改,就会在恢复行时发生 CancelRowEdit 事件。如果提交范围是在运行时确定的,则此事件非常有用。
Private Sub dataGridView1_RowDirtyStateNeeded(ByVal sender As Object, _ ByVal e As System.Windows.Forms.QuestionEventArgs) _ Handles dataGridView1.RowDirtyStateNeeded If Not rowScopeCommit Then ' In cell-level commit scope, indicate whether the value ' of the current cell has been modified. e.Response = Me.dataGridView1.IsCurrentCellDirty End If End Sub
private void dataGridView1_RowDirtyStateNeeded(object sender, System.Windows.Forms.QuestionEventArgs e) { if (!rowScopeCommit) { // In cell-level commit scope, indicate whether the value // of the current cell has been modified. e.Response = this.dataGridView1.IsCurrentCellDirty; } }
为 CancelRowEdit 事件实现一个处理程序,丢弃表示当前行的 Customer 对象的值。
当用户通过在编辑模式下按 Esc 两次或在非编辑模式下按 Esc 一次来指示行恢复时,会发生此事件。如果当前行中没有任何单元格被修改,或者,QuestionEventArgs.Response 属性的值已在 RowDirtyStateNeeded 事件处理程序中设置为 false,则不会发生此事件。
Private Sub dataGridView1_CancelRowEdit(ByVal sender As Object, _ ByVal e As System.Windows.Forms.QuestionEventArgs) _ Handles dataGridView1.CancelRowEdit If Me.rowInEdit = Me.dataGridView1.Rows.Count - 2 AndAlso _ Me.rowInEdit = Me.customers.Count Then ' If the user has canceled the edit of a newly created row, ' replace the corresponding Customer object with a new, empty one. Me.customerInEdit = New Customer() Else ' If the user has canceled the edit of an existing row, ' release the corresponding Customer object. Me.customerInEdit = Nothing Me.rowInEdit = -1 End If End Sub
private void dataGridView1_CancelRowEdit(object sender, System.Windows.Forms.QuestionEventArgs e) { if (this.rowInEdit == this.dataGridView1.Rows.Count - 2 && this.rowInEdit == this.customers.Count) { // If the user has canceled the edit of a newly created row, // replace the corresponding Customer object with a new, empty one. this.customerInEdit = new Customer(); } else { // If the user has canceled the edit of an existing row, // release the corresponding Customer object. this.customerInEdit = null; this.rowInEdit = -1; } }
为 UserDeletingRow 事件实现一个处理程序,该事件将现有的 Customer 对象从数据存储区中删除,或丢弃表示新建行的未保存的 Customer 对象。
只要用户通过单击行标题再按 Delete 键来删除行,就会发生此事件。
Private Sub dataGridView1_UserDeletingRow(ByVal sender As Object, _ ByVal e As System.Windows.Forms.DataGridViewRowCancelEventArgs) _ Handles dataGridView1.UserDeletingRow If e.Row.Index < Me.customers.Count Then ' If the user has deleted an existing row, remove the ' corresponding Customer object from the data store. Me.customers.RemoveAt(e.Row.Index) End If If e.Row.Index = Me.rowInEdit Then ' If the user has deleted a newly created row, release ' the corresponding Customer object. Me.rowInEdit = -1 Me.customerInEdit = Nothing End If End Sub
private void dataGridView1_UserDeletingRow(object sender, System.Windows.Forms.DataGridViewRowCancelEventArgs e) { if (e.Row.Index < this.customers.Count) { // If the user has deleted an existing row, remove the // corresponding Customer object from the data store. this.customers.RemoveAt(e.Row.Index); } if (e.Row.Index == this.rowInEdit) { // If the user has deleted a newly created row, release // the corresponding Customer object. this.rowInEdit = -1; this.customerInEdit = null; } }
实现一个简单的 Customers 类来表示此代码示例使用的数据项。
Public Class Customer Private companyNameValue As String Private contactNameValue As String Public Sub New() ' Leave fields empty. End Sub Public Sub New(ByVal companyName As String, ByVal contactName As String) companyNameValue = companyName contactNameValue = contactName End Sub Public Property CompanyName() As String Get Return companyNameValue End Get Set(ByVal value As String) companyNameValue = value End Set End Property Public Property ContactName() As String Get Return contactNameValue End Get Set(ByVal value As String) contactNameValue = value End Set End Property End Class
public class Customer { private String companyNameValue; private String contactNameValue; public Customer() { // Leave fields empty. } public Customer(String companyName, String contactName) { companyNameValue = companyName; contactNameValue = contactName; } public String CompanyName { get { return companyNameValue; } set { companyNameValue = value; } } public String ContactName { get { return contactNameValue; } set { contactNameValue = value; } } }
测试应用程序
现在可以测试窗体,以确保它的行为与预期相同。
测试窗体
编译并运行应用程序。
您将看到填入了三个客户记录的 DataGridView 控件。您可以修改一行中的多个单元格的值,然后在编辑模式下按 Esc 两次或在非编辑模式下按 Esc 一次,将整行恢复为原始值。当在控件中修改、添加或删除行时,数据存储区中的 Customer 对象也会相应地修改、添加或删除。
后续步骤
此应用程序使您可以基本了解在 DataGridView 控件中实现虚拟模式所必须处理的事件。可以通过许多方法来改进这个简单的应用程序:
实现用于缓存来自外部数据库的值的数据存储区。此缓存应根据需要检索和丢弃值,以便仅仅包含必须显示的内容,从而确保只占用客户端计算机上的少量内存。
根据实际要求精细调整数据存储区的性能。例如,您可能要使用更大规模的缓存并将数据库查询的数目控制在最少,从而补偿慢速网络连接,而不是根据客户端计算机的内存限制进行调整。
有关缓存来自外部数据库的值的更多信息,请参见 如何:在 Windows 窗体 DataGridView 控件中实现实时数据加载的虚拟模式。
请参见
任务
如何:在 Windows 窗体 DataGridView 控件中实现虚拟模式
概念
缩放 Windows 窗体 DataGridView 控件的最佳做法
在 Windows 窗体 DataGridView 控件中实现实时数据加载的虚拟模式