How to populate a Datagrid control on a background thread with data binding by using Visual C++ .NET or Visual C++ 2005 (816177)



The information in this article applies to:

  • Microsoft Visual C++ 2005 Express Edition
  • Microsoft Visual C++ .NET (2003)
  • Microsoft ADO.NET (included with the .NET Framework) 1.0
  • Microsoft ADO.NET (included with the .NET Framework 1.1)

For a Microsoft Visual Basic .NET version of this article, see 318604.
For a Microsoft Visual C# .NET version of this article, see 318607.
This article refers to the following Microsoft .NET Framework Class Library namespaces:
  • System::Threading
  • System::Data
  • System::Data::SqlClient

IN THIS TASK

SUMMARY

This step-by-step article describes how to query a database on a background thread, and how to use data binding to display the results of the query in a DataGrid object.

When you run large queries to a database, your application may become unresponsive for a long time. To avoid this behavior, run large queries on a background thread. This decreases your waiting time, releases your application for other tasks until the database returns the data, and also performs data binding.

back to the top

Requirements

The following list outlines the recommended hardware, software, network infrastructure, and service packs that you need:
  • Microsoft Visual Studio .NET 2003 or Microsoft Visual Studio 2005
  • Access to the SQL Northwind sample database
back to the top

Background

By design, a thread (other than the thread that created a form or a control) cannot call Windows Forms methods or Windows Control methods. If you try to do this, your application throws an exception. Depending on the exception-handling that is implemented in your code, this exception may cause your application to quit. If your application does not implement exception-handling, you receive the following error message:
An unhandled exception of type 'System.ArgumentException' occurred in system.windows.forms.dll
Additional information: Controls created on one thread cannot be parented to a control on a different thread.
This exception occurs because Windows Forms are based on a single-threaded apartment (STA) model. Windows Forms can be created on any thread. However, after they are created, they cannot be switched to a different thread. Additionally, Windows Form methods cannot be accessed on another thread. This means that all method calls must be run on the thread that created the form or the control.

Method calls that originate outside a creating thread must be marshalled (run) on this creating thread. To do this asynchronously, the form has a BeginInvoke method that forces the method to run on the thread that created the form or control. You call a synchronous method with a call to the Invoke method.

back to the top

Build a Windows Forms Application

This section describes how to create a Windows Forms application that queries a database on a background thread, and uses the BeginInvoke method to perform data binding on a DataGrid object.
  1. Start Visual Studio .NET 2003 or Visual Studio 2005.
  2. On the File menu, point to New, and then click Project.
  3. Click Visual C++ Projects under Project Types, and then click Windows Forms Application (.NET) under Templates. By default, Form1 is created.

    Note In Visual Studio 2005, click Visual C++ under Project Types, and then click Windows Forms Application under Templates.
  4. Add a Button control to Form1, and then change the Text property of the button to Query on Thread.
  5. Add another Button control to Form1, and then change the Text property of the button to Query on Form.
  6. Add a Label control to Form1, and then clear the Text property of the label.
  7. Add a TextBox control to Form1.
  8. Add a DataGrid control to Form1.
  9. Right-click Form1, and then click View Code to display the code of your application.
  10. Add the following using statements at the top of the code window to use the System::Threading namespace and the System::Data::SqlClient namespace:
    using namespace System::Threading;
    using namespace System::Data::SqlClient;
  11. Add the following code to the Form1.h file:
    static Form1 * MyForm; 
    Thread  * UpdateThread;
    static ThreadStart * UpdateThreadStart = new ThreadStart(0,&Form1::QueryDataBase);
    static MethodInvoker  * CallDataBindToDataGrid = new MethodInvoker(0,&Form1::DataBindToDataGrid);
    static DataSet * MyDataSet;
    static SqlDataAdapter * MyDataAdapter;
    static String * MyQueryString = S"SELECT [Order Details].*, Orders.CustomerID, Orders.EmployeeID, Orders.OrderDate FROM [Order Details] CROSS JOIN Orders";
    static SqlConnection * MyConnection = new SqlConnection("data source=localhost;initial catalog=Northwind;integrated security=SSPI;");
    Note Change the connection string in this code according to your environment. The query that is used in this sample is a Cartesian product that returns over 165,000 rows from the Northwind database. The query returns a large amount of data so that the responsiveness of the form can be demonstrated.
  12. Open the Windows Forms Design view.
  13. Double-click Query on Thread, and then add the following code in the Click event for this button control:
    UpdateThread = new Thread(UpdateThreadStart);
    UpdateThread->Name = S"Update Thread";
    UpdateThread->IsBackground = true;
    UpdateThread->Start();
  14. Open the Windows Forms Design view.
  15. Double-click Query on Form, and then add the following code in the Click event for this button control:
    QueryDataBase();
  16. Add the following code after the code that you added in the previous step:
    // Subroutine that is to be run on the thread of the form.
    static void DataBindToDataGrid()
    {
    	MyForm->dataGrid1->DataSource = MyDataSet;
    	MyForm->dataGrid1->DataMember = S"MyTable";
    	MyDataSet = NULL;
    	MyDataAdapter = NULL;
    }
    
    // Subroutine that is used by the background thread to query the database.
    static void QueryDataBase()
    {
    	MyDataSet = new DataSet();
    	MyConnection->Open();
    	MyDataAdapter = new SqlDataAdapter(MyQueryString, MyConnection);		
    	MyForm->label1->Text = S"Filling the DataSet";
    	MyDataAdapter->Fill(MyDataSet, S"MyTable");
    	MyConnection->Close();
    	MyForm->label1->Text = S"DataSet Filled";
    	MyForm->BeginInvoke(CallDataBindToDataGrid);	
    }
    These subroutines are used by the background thread to query the database. These subroutines are also used to bind the database to the DataGrid control that is located on Form1 when you click the first button. The Click event of the second button directly calls the QueryDataBase function, and will be run on the Windows Form thread.
  17. Add the following line of code at the end of the InitializeComponent function:
    MyForm = this;
  18. Double-click Form1.cpp in Solution Explorer, and then modify the _tWinMain function as follows:
    Form1 * MyForm;
    MyForm = new Form1();
    Application::Run(MyForm);
  19. Press the CTRL+SHIFT+B key combination to build your application.
back to the top

Demonstration

To see the benefit that is gained by using a background thread to query a database, follow these steps:
  1. Press the CTRL+F5 key combination to run your application without debugging.
  2. Click Query on Form. This begins the query on the Windows Form thread. If you try to enter some text in the text box that is displayed on Form1, your application does not respond. After the query has completed (this may take some time, depending on your computer), the DataGrid object displays the results of the query.
  3. Click Query on Thread. This creates a background thread that queries the database, and also keeps your application responsive to user interaction. To see this, click Query on Thread, and then try to type some text in the text box on the Form1 form.
back to the top

Complete Code Listing

Form1.h

#pragma once

namespace DataGridThread
{
    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Drawing;
    using namespace System::Threading;
    using namespace System::Data::SqlClient;

    /// <summary> 
    /// Summary for Form1
    /// </summary>
public __gc class Form1 : public System::Windows::Forms::Form
{   
public:
    Form1(void)
    {
        InitializeComponent();
    }

protected:
    void Dispose(Boolean disposing)
    {
        if (disposing && components)
        {
            components->Dispose();
        }
        __super::Dispose(disposing);
    }
private: System::Windows::Forms::Button *  button1;
private: System::Windows::Forms::Button *  button2;
private: System::Windows::Forms::Label *  label1;
private: System::Windows::Forms::TextBox *  textBox1;
private: System::Windows::Forms::DataGrid *  dataGrid1;

private:
    /// <summary>
    /// Required designer variable.
    /// </summary>
    System::ComponentModel::Container * components;

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    void InitializeComponent(void)
    {
        this->button1 = new System::Windows::Forms::Button();
        this->button2 = new System::Windows::Forms::Button();
        this->label1 = new System::Windows::Forms::Label();
        this->textBox1 = new System::Windows::Forms::TextBox();
        this->dataGrid1 = new System::Windows::Forms::DataGrid();
        (__try_cast<System::ComponentModel::ISupportInitialize *  >(this->dataGrid1))->BeginInit();
        this->SuspendLayout();
        // 
        // button1
        // 
        this->button1->Location = System::Drawing::Point(180, 16);
        this->button1->Name = S"button1";
        this->button1->Size = System::Drawing::Size(104, 23);
        this->button1->TabIndex = 0;
        this->button1->Text = S"Query on Thread";
        this->button1->Click += new System::EventHandler(this, button1_Click);
        // 
        // button2
        // 
        this->button2->Location = System::Drawing::Point(316, 16);
        this->button2->Name = S"button2";
        this->button2->Size = System::Drawing::Size(104, 23);
        this->button2->TabIndex = 1;
        this->button2->Text = S"Query on Form";
        this->button2->Click += new System::EventHandler(this, button2_Click);
        // 
        // label1
        // 
        this->label1->Location = System::Drawing::Point(24, 48);
        this->label1->Name = S"label1";
        this->label1->Size = System::Drawing::Size(496, 23);
        this->label1->TabIndex = 2;
        // 
        // textBox1
        // 
        this->textBox1->Location = System::Drawing::Point(24, 80);
        this->textBox1->Name = S"textBox1";
        this->textBox1->TabIndex = 3;
        this->textBox1->Text = S"textBox1";
        // 
        // dataGrid1
        // 
        this->dataGrid1->DataMember = S"";
        this->dataGrid1->HeaderForeColor = System::Drawing::SystemColors::ControlText;
        this->dataGrid1->Location = System::Drawing::Point(8, 112);
        this->dataGrid1->Name = S"dataGrid1";
        this->dataGrid1->Size = System::Drawing::Size(584, 296);
        this->dataGrid1->TabIndex = 4;
        // 
        // Form1
        // 
        this->AutoScaleBaseSize = System::Drawing::Size(5, 13);
        this->ClientSize = System::Drawing::Size(600, 421);
        this->Controls->Add(this->dataGrid1);
        this->Controls->Add(this->textBox1);
        this->Controls->Add(this->label1);
        this->Controls->Add(this->button2);
        this->Controls->Add(this->button1);
        this->Name = S"Form1";
        this->Text = S"Form1";
        (__try_cast<System::ComponentModel::ISupportInitialize *  >(this->dataGrid1))->EndInit();
        this->ResumeLayout(false);
        MyForm = this;

    }
    static Form1 * MyForm; 

    Thread  * UpdateThread;
    static ThreadStart * UpdateThreadStart = new ThreadStart(0,&Form1::QueryDataBase);
    static MethodInvoker  * CallDataBindToDataGrid = new MethodInvoker(0,&Form1::DataBindToDataGrid);
    static DataSet * MyDataSet;
    static SqlDataAdapter * MyDataAdapter;
    static String * MyQueryString = S"SELECT [Order Details].*, Orders.CustomerID, Orders.EmployeeID, Orders.OrderDate FROM [Order Details] CROSS JOIN Orders";
    static SqlConnection * MyConnection = new SqlConnection("data source=localhost;initial catalog=Northwind;integrated security=SSPI;");

private: System::Void button1_Click(System::Object *  sender, System::EventArgs *  e)
         {
             UpdateThread = new Thread(UpdateThreadStart);
             UpdateThread->Name = S"Update Thread";
             UpdateThread->IsBackground = true;
             UpdateThread->Start();
         }
private: System::Void button2_Click(System::Object *  sender, System::EventArgs *  e)
         {
             QueryDataBase();
         }
         // Subroutine that is to be run on the form's thread.
         static void DataBindToDataGrid()
         {
             MyForm->dataGrid1->DataSource = MyDataSet;
             MyForm->dataGrid1->DataMember = S"MyTable";
             MyDataSet = NULL;
             MyDataAdapter = NULL;
         }

         // Subroutine that is used by the background thread to query the database.
         static void QueryDataBase()
         {
             MyDataSet = new DataSet();
             MyConnection->Open();
             MyDataAdapter = new SqlDataAdapter(MyQueryString, MyConnection);       
             MyForm->label1->Text = S"Filling the DataSet";
             MyDataAdapter->Fill(MyDataSet, S"MyTable");
             MyConnection->Close();
             MyForm->label1->Text = S"DataSet Filled";
             MyForm->BeginInvoke(CallDataBindToDataGrid);   
         }
    };
}
Note You must add the common language runtime support compiler option (/clr:oldSyntax) in Visual C++ 2005 to successfully compile the previous code sample. To add the common language runtime support compiler option in Visual C++ 2005, follow these steps:
  1. Click Project, and then click <ProjectName> Properties.

    Note <ProjectName> is a placeholder for the name of the project.
  2. Expand Configuration Properties, and then click General.
  3. Click to select Common Language Runtime Support, Old Syntax (/clr:oldSyntax) in the Common Language Runtime support project setting in the right pane, click Apply, and then click OK.
For more information about the common language runtime support compiler option, visit the following Microsoft Web site:

/clr (Common Language Runtime Compilation)
http://msdn2.microsoft.com/en-us/library/k8d11d4s.aspx

Form1.cpp

#include "stdafx.h"
#include "Form1.h"
#include <windows.h>

using namespace DataGridThread;

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    Form1 * MyForm;
    MyForm = new Form1();
    Application::Run(MyForm);
}
back to the top

REFERENCES

For more information, visit the following Microsoft Developer Network (MSDN) Web site:back to the top

Modification Type:MajorLast Reviewed:1/5/2006
Keywords:kberrmsg kbDataAdapter kbSqlClient kbThreadSync kbThread kbHOWTOmaster KB816177 kbAudDeveloper