Export (0) Print
Expand All
4 out of 5 rated this helpful - Rate this topic

Walkthrough: Implementing Virtual Mode in the Windows Forms DataGridView Control

When you want to display very large quantities of tabular data in a DataGridView control, you can set the VirtualMode property to true and explicitly manage the control's interaction with its data store. This lets you fine-tune the performance of the control in this situation.

The DataGridView control provides several events that you can handle to interact with a custom data store. This walkthrough guides you through the process of implementing these event handlers. The code example in this topic uses a very simple data source for illustration purposes. In a production setting, you will typically load only the rows you need to display into a cache, and handle DataGridView events to interact with and update the cache. For more information, see Implementing Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control

To copy the code in this topic as a single listing, see How to: Implement Virtual Mode in the Windows Forms DataGridView Control.

To implement virtual mode

  1. Create a class that derives from Form and contains a DataGridView control.

    The following code contains some basic initialization. It declares some variables that will be used in later steps, provides a Main method, and provides a simple form layout in the class constructor.

    #using <System.Drawing.dll>
    #using <System.dll>
    #using <System.Windows.Forms.dll>
    
    using namespace System;
    using namespace System::Windows::Forms;
    
    public ref class Form1: public Form
    {
    private:
       DataGridView^ dataGridView1;
    
       // Declare an ArrayList to serve as the data store. 
       System::Collections::ArrayList^ customers;
    
       // Declare a Customer object to store data for a row being edited.
       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. 
       int rowInEdit;
    
       // Declare a variable to indicate the commit scope. 
       // Set this value to false to use cell-level commit scope. 
       bool rowScopeCommit;
    
    public:
       static void Main()
       {
          Application::Run( gcnew Form1 );
       }
    
       Form1()
       {
          dataGridView1 = gcnew DataGridView;
          customers = gcnew System::Collections::ArrayList;
          rowInEdit = -1;
          rowScopeCommit = true;
    
          // Initialize the form.
          this->dataGridView1->Dock = DockStyle::Fill;
          this->Controls->Add( this->dataGridView1 );
          this->Load += gcnew EventHandler( this, &Form1::Form1_Load );
       }
    
    private:
    
    
    ...
    
    
    };
    
    int main()
    {
       Form1::Main();
    }
    
  2. Implement a handler for your form's Load event that initializes the DataGridView control and populates the data store with sample values.

    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 += gcnew
           DataGridViewCellValueEventHandler( this, &Form1::dataGridView1_CellValueNeeded );
       this->dataGridView1->CellValuePushed += gcnew
           DataGridViewCellValueEventHandler( this, &Form1::dataGridView1_CellValuePushed );
       this->dataGridView1->NewRowNeeded += gcnew
           DataGridViewRowEventHandler( this, &Form1::dataGridView1_NewRowNeeded );
       this->dataGridView1->RowValidated += gcnew
           DataGridViewCellEventHandler( this, &Form1::dataGridView1_RowValidated );
       this->dataGridView1->RowDirtyStateNeeded += gcnew
           QuestionEventHandler( this, &Form1::dataGridView1_RowDirtyStateNeeded );
       this->dataGridView1->CancelRowEdit += gcnew
           QuestionEventHandler( this, &Form1::dataGridView1_CancelRowEdit );
       this->dataGridView1->UserDeletingRow += gcnew
           DataGridViewRowCancelEventHandler( this, &Form1::dataGridView1_UserDeletingRow );
    
       // Add columns to the DataGridView.
       DataGridViewTextBoxColumn^ companyNameColumn = gcnew DataGridViewTextBoxColumn;
       companyNameColumn->HeaderText = L"Company Name";
       companyNameColumn->Name = L"Company Name";
       companyNameColumn->AutoSizeCriteria = DataGridViewAutoSizeColumnCriteria::HeaderAndRows;
       DataGridViewTextBoxColumn^ contactNameColumn = gcnew DataGridViewTextBoxColumn;
       contactNameColumn->HeaderText = L"Contact Name";
       contactNameColumn->Name = L"Contact Name";
       contactNameColumn->AutoSizeCriteria = DataGridViewAutoSizeColumnCriteria::HeaderAndRows;
       this->dataGridView1->Columns->Add( companyNameColumn );
       this->dataGridView1->Columns->Add( contactNameColumn );
    
       // Add some sample entries to the data store.  
       this->customers->Add( gcnew Customer( L"Bon app'",L"Laurence Lebihan" ) );
       this->customers->Add( gcnew Customer( L"Bottom-Dollar Markets",L"Elizabeth Lincoln" ) );
       this->customers->Add( gcnew Customer( L"B's Beverages",L"Victoria Ashworth" ) );
    
       // Set the row count, including the row for new records. 
       this->dataGridView1->RowCount = 4;
    }
    
  3. Implement a handler for the CellValueNeeded event that retrieves the requested cell value from the data store or the Customer object currently in edit.

    This event occurs whenever the DataGridView control needs to paint a cell.

    void dataGridView1_CellValueNeeded( Object^ /*sender*/,
        System::Windows::Forms::DataGridViewCellValueEventArgs^ e )
    {
       Customer^ customerTmp = nullptr;
    
       // Store a reference to the Customer object for the row being painted. 
       if ( e->RowIndex == rowInEdit )
       {
          customerTmp = this->customerInEdit;
       }
       else
       {
          customerTmp = dynamic_cast<Customer^>(this->customers[ e->RowIndex ]);
       }
    
       // Set the cell value to paint using the Customer object retrieved. 
       int switchcase = 0;
       if ( (this->dataGridView1->Columns[ e->ColumnIndex ]->Name)->Equals( L"Company Name" ) )
             switchcase = 1;
       else 
       if ( (this->dataGridView1->Columns[ e->ColumnIndex ]->Name)->Equals( L"Contact Name" ) )
             switchcase = 2;
    
    
       switch ( switchcase )
       {
          case 1:
             e->Value = customerTmp->CompanyName;
             break;
    
          case 2:
             e->Value = customerTmp->ContactName;
             break;
       }
    }
    
  4. Implement a handler for the CellValuePushed event that stores an edited cell value in the Customer object representing the edited row. This event occurs whenever the user commits a cell value change.

    void dataGridView1_CellValuePushed( Object^ /*sender*/,
        System::Windows::Forms::DataGridViewCellValueEventArgs^ e )
    {
       Customer^ customerTmp = nullptr;
    
       // 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 == nullptr )
          {
             this->customerInEdit = gcnew Customer(
                 (dynamic_cast<Customer^>(this->customers[ e->RowIndex ]))->CompanyName,
                 (dynamic_cast<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. 
       int switchcase = 0;
       if ( (this->dataGridView1->Columns[ e->ColumnIndex ]->Name)->Equals( L"Company Name" ) )
             switchcase = 1;
       else 
       if ( (this->dataGridView1->Columns[ e->ColumnIndex ]->Name)->Equals( L"Contact Name" ) )
             switchcase = 2;
    
    
       switch ( switchcase )
       {
          case 1:
             customerTmp->CompanyName = dynamic_cast<String^>(e->Value);
             break;
    
          case 2:
             customerTmp->ContactName = dynamic_cast<String^>(e->Value);
             break;
       }
    }
    
  5. Implement a handler for the NewRowNeeded event that creates a new Customer object representing a newly created row.

    This event occurs whenever the user enters the row for new records.

    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 = gcnew Customer;
       this->rowInEdit = this->dataGridView1->Rows->Count - 1;
    }
    
  6. Implement a handler for the RowValidated event that saves new or modified rows to the data store.

    This event occurs whenever the user changes the current row.

    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 = nullptr;
          this->rowInEdit = -1;
       }
       else 
       if ( this->customerInEdit != nullptr && e->RowIndex < this->customers->Count )
       {
    
          // Save the modified Customer object in the data store. 
          this->customers[ e->RowIndex ] = this->customerInEdit;
          this->customerInEdit = nullptr;
          this->rowInEdit = -1;
       }
       else 
       if ( this->dataGridView1->ContainsFocus )
       {
          this->customerInEdit = nullptr;
          this->rowInEdit = -1;
       }
    }
    
  7. Implement a handler for the RowDirtyStateNeeded event that indicates whether the CancelRowEdit event will occur when the user signals row reversion by pressing ESC twice in edit mode or once outside of edit mode.

    By default, CancelRowEdit occurs upon row reversion when any cells in the current row have been modified unless the QuestionEventArgs::Response property is set to true in the RowDirtyStateNeeded event handler. This event is useful when the commit scope is determined at run time.

    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;
       }
    }
    
  8. Implement a handler for the CancelRowEdit event that discards the values of the Customer object representing the current row.

    This event occurs when the user signals row reversion by pressing ESC twice in edit mode or once outside of edit mode. This event does not occur if no cells in the current row have been modified or if the value of the QuestionEventArgs::Response property has been set to false in a RowDirtyStateNeeded event handler.

    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 = gcnew Customer;
       }
       else
       {
    
          // If the user has canceled the edit of an existing row,  
          // release the corresponding Customer object. 
          this->customerInEdit = nullptr;
          this->rowInEdit = -1;
       }
    }
    
  9. Implement a handler for the UserDeletingRow event that deletes an existing Customer object from the data store or discards an unsaved Customer object representing a newly created row.

    This event occurs whenever the user deletes a row by clicking a row header and pressing the DELETE key.

    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 = nullptr;
       }
    }
    
  10. Implement a simple Customers class to represent the data items used by this code example.

    public ref class Customer
    {
    private:
       String^ companyNameValue;
       String^ contactNameValue;
    
    public:
       Customer()
       {
    
          // Leave fields empty.
       }
    
       Customer( String^ companyName, String^ contactName )
       {
          companyNameValue = companyName;
          contactNameValue = contactName;
       }
    
    
       property String^ CompanyName 
       {
          String^ get()
          {
             return companyNameValue;
          }
    
          void set( String^ value )
          {
             companyNameValue = value;
          }
    
       }
    
       property String^ ContactName 
       {
          String^ get()
          {
             return contactNameValue;
          }
    
          void set( String^ value )
          {
             contactNameValue = value;
          }
    
       }
    
    };
    

You can now test the form to make sure it behaves as expected.

To test the form

  • Compile and run the application.

    You will see a DataGridView control populated with three customer records. You can modify the values of multiple cells in a row and press ESC twice in edit mode and once outside of edit mode to revert the entire row to its original values. When you modify, add, or delete rows in the control, Customer objects in the data store are modified, added, or deleted as well.

This application gives you a basic understanding of the events you must handle to implement virtual mode in the DataGridView control. You can improve this basic application in a number of ways:

  • Implement a data store that caches values from an external database. The cache should retrieve and discard values as necessary so that it only contains what is necessary for display while consuming a small amount of memory on the client computer.

  • Fine-tune the performance of the data store depending on your requirements. For example, you might want to compensate for slow network connections rather than client-computer memory limitations by using a larger cache size and minimizing the number of database queries.

For more information about caching values from an external database, see How to: Implement Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback

Community Additions

ADD
Show:
© 2014 Microsoft. All rights reserved.