C# - Buscando e exibindo Registros em um DataGridView Usando BackgroundWorker


Hoje eu vou voltar a escrever sobre o controle BackGroundWorker.

O controle BackgroundWorker pode executar tarefas com processamento intensivo em uma thread separada de forma que a interface do usuário (UI) não congela durante o processamento.

O componente BackgroundWorker componente oferece a capacidade de executar operações demoradas assincronamente ("em segundo plano"), em uma thread diferente do segmento de interface do usuário principal do seu aplicativo.

Para usar um BackgroundWorker, você simplesmente diz a ele que método deseja que seja executado em segundo plano e, em seguida, chame o método RunWorkerAsync.

A thread chamada continua a ser executada normalmente enquanto o método worker é executado de forma assíncrona. Quando o método for concluído, o BackgroundWorker alerta a thread chamada, acionando o evento RunWorkerCompleted, que contém, opcionalmente, os resultados da operação.

Desde que essas tarefas estão sendo executados em segundo plano e podem levar algum tempo, é necessário mostrar algumas mensagens personalizadas e atualizar a interface do usuário enquanto o trabalho esta em execução.

Vamos criar um projeto Windows Forms usando o Visual C# 2010 Express Edition que irá acessar um banco de dados SQL Server e exibir os dados de uma tabela em um controle DataGridView. Vamos usar os eventos do controle BackGroundWorker para exibir mensagens ao usuário durante o processamento.

Estamos usando a tabela Funcionarios do banco de dados Teste.mdf que possui a seguinte estrutura:

Obs: Vamos definir a classe TabelaDados com os campos Id e nomeFuncionario para exibir somente os dois campos pertinentes da tabela.

Você pode usar qualquer banco de dados e tabela para preencher o controle DataGridView.

Crie então um novo projeto Windows Forms Application (menu File-> New Project) e informe o nome BackGroundWorkerExemplo;

A seguir vamos incluir a partir da ToolBox no formulário form1.cs os seguintes controles/componentes:

Disponha os controles conforme o leiaute abaixo:

Vejamos as propriedades e métodos do controle BackGroundWorker que iremos usar em nosso projeto:

Propriedades:

Métodos:

Eventos:

1- DoWork - realiza o trabalho de execução da thread em background sendo chamado quando o método RunWorkerAsync é acionado;

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
     //Logica para tarefa em background ser feita
}

O DoWorkEventArgs e possui as propriedades e.Argument e e.Result:

2- ProgressChanged - reporta o progresso feito pela thread. Este evento é disparado a partir do evento DoWork usando o método ReportProgress();

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
         // Reporta o progresso do BackgroundWorker
}

O ProgressChangedEventArgs contém as propriedades e.ProgressPercentage e e.UserState:

3- RunWorkerCompleted - Este evento é executado quando o BackgroundWorker terminou o a tarefa. Também é disparado quando o BackgroundWorker falhou ao realizar a tarefa atribuida ou a tarefa foi cancelada;

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
     //Executado no termino de operação BackgroundTask/ Failure/ Cancellation.
}

RunWorkerCompletedEventArgs e possui as seguinte propriedades:

Vamos declarar os seguintes namespaces no formulário:

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Threading;

A seguir declare a string de conexão a uma variável o para obter o total de registros:

SqlCommand Sqlcmd;
string ConnString =
@"Data Source=.\SQLEXPRESS;AttachDbFilename=C:\dados\Teste.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True";
int _TotalRegistros;

No construtor do formulário

public frmDados()
 {
            InitializeComponent();
            // Para ativar o relatório de progresso do background worker precisamos definir esta propriedade
            backgroundWorker1.WorkerReportsProgress = true;
 }

Estamos ativando o componente backgroundWorker.

No evento Load do formulário configuramos alguns controles como o botão Cancelar, a barra de status e o controle DataGridView;

 private void frmRetrive_Load(object sender, EventArgs e)
 {
            btnCancelar.Enabled = false;
            statusStrip1.Visible = false;
            toolStripStatusLabel1.Visible = false;

            dataGridView1.ColumnCount = 2;
            dataGridView1.Columns[0].Name = "No.";
            dataGridView1.Columns[0].Width = 150;
            dataGridView1.Columns[1].Width = 150;
            dataGridView1.RowHeadersWidth = 21;
            dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
            dataGridView1.ColumnHeadersHeight = 23;
            dataGridView1.Columns[1].Name = "Nome Funcionário";
}

No evento Click do botão Executar usamos a propriedade IsBusy para indicar se o controle backgroundworker esta realizando uma operação assíncrona ou operação em background;

Limpamos o controle DataGridView e iniciamos a thread em background:

private void btnIniciar_Click(object sender, EventArgs e)
        {
            statusStrip1.Visible = true;
            toolStripStatusLabel1.Visible = true;
            toolStripProgressBar1.Maximum = GeTotalRegistros();

            if (!backgroundWorker1.IsBusy)
            {
                TabelaDados TObj = new TabelaDados();
                dataGridView1.Rows.Clear();
                // Inicia a Thread em BackGround 
                backgroundWorker1.RunWorkerAsync(TObj);
                btnIniciar.Enabled = false;
                btnCancelar.Enabled = true;
            }
        }

No evento DoWork executamos a tarefa em segundo plano exibindo o seu processamento na barra de progresso:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
 { 
            TabelaDados Obj = (TabelaDados)e.Argument;
            string SqlcmdString = "SELECT id,funci_nome FROM Funcionarios";
            SqlDataReader reader;
            int i = 1;
            try
            {
                using (SqlConnection conn = new SqlConnection(ConnString))
                {
                    Sqlcmd = new SqlCommand(SqlcmdString, conn);
                    conn.Open();
                    reader = Sqlcmd.ExecuteReader();

                    if (reader.HasRows)
                    {
                        while (reader.Read())
                        {
                            Obj.id = reader["id"].ToString();
                            Obj.nomeFuncionario = reader["funci_nome"].ToString();
                            // força uma delay no processamento
                            Thread.Sleep(2000);
                            // Relatorio de progresso
                            backgroundWorker1.ReportProgress(i,Obj);

                            if (backgroundWorker1.CancellationPending)
                            {
                                // Define a flag e.Cancel de forma que o evento WorkerCompleted
                                // saiba que o processo foi cancelado
                                e.Cancel = true;
                                backgroundWorker1.ReportProgress(0);
                                return;
                            }
                            i = i + 1;
                        }
                        conn.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
   }

No evento RunWorkerCompleted verificamos se a tarefa foi concluída ou cancelada:

 private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
            if (e.Cancelled)
            {   
                toolStripStatusLabel1.Text = "Cancelado pelo usuáio...";
                toolStripProgressBar1.Value = 0; 
            }
            // Verifica se ocorreu algum  erro no processo em background
            else if (e.Error != null)
            { 
                toolStripStatusLabel1.Text = e.Error.Message;
            }
            else
            {
                // A tarefa em BackGround foi completada sem erros
                toolStripStatusLabel1.Text = " Todos os registros foram carregados...";
            }
   }

O evento ProgressChanged exibimos os dados no controle DataGridView se a tarefa não foi cancelada:

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (!backgroundWorker1.CancellationPending)
            {
                //Obtem o status do usuário que enviado como parte do  método ReportProgress() a partir do evento DoWork
                TabelaDados Obj = (TabelaDados)e.UserState;
                //Inclui os dados no dataGridView1
                dataGridView1.Rows.Add(Obj.id.ToString(),Obj.nomeFuncionario.ToString()); 
                toolStripProgressBar1.Value = e.ProgressPercentage;
                toolStripStatusLabel1.Text = "Processando Linha.. " + e.ProgressPercentage.ToString() +  " de " +_TotalRegistros;
            }
        }

No evento Click do botão Cancelar verificamos se o processo esta em execução e paramos a execução da thread:

private void btnCancelar_Click(object sender, EventArgs e)
        {
            //verifica se o processo background Process esta em execução
            if (backgroundWorker1.IsBusy)
            {
                // Para a execução da Thread Background
                backgroundWorker1.CancelAsync();
                btnCancelar.Enabled = false;
                btnIniciar.Enabled = true;
            }            
        }

A rotina GetTotalRegistros obtém o total de registros da tabela acessada:

 private int GeTotalRegistros()
 {
            SqlConnection con;
            SqlCommand cmd;
            try
            {
                using (con = new SqlConnection(ConnString))
                {
                    cmd = new SqlCommand("SELECT COUNT(*) FROM Funcionarios", con);
                    con.Open();
                    _TotalRegistros = int.Parse(cmd.ExecuteScalar().ToString());
                    con.Close();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            return _TotalRegistros;
  }

A classe usada para criar a tabela e exibir os dados no DataGridView:

 public class TabelaDados
  {
            public string id;
            public string nomeFuncionario;
 }

No evento FormClosing (este evento ocorre antes do formulário ser fechado) do formulário frmDados

Obs: Quando um formulário é fechado, ele é descartado, liberando todos os recursos associados ao formulário.Se você cancelar este evento, o formulário permanece aberto.Para cancelar o fechamento de um formulário, defina como true a propriedade Cancel do FormClosingEventArgs passado para o manipulador de eventos.

Nota: Lembrando também que os eventos Form.Closed e Form.Closing não são disparados quando o método Application.Exit é chamado para encerrar a aplicação.

    private void frmDados_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            {
                if (backgroundWorker1.IsBusy)
                {
                    backgroundWorker1.CancelAsync();
                    btnCancelar.Enabled = false;
                    btnIniciar.Enabled = true;
                }
            }
        }

A propriedade CloseReason retorna vários valores conforme o motivo pelo qual o formulário esta sendo fechado como fazia o VB6 com o QueryUnload.

Vejamos então quais os valores da enumeração CloseReason :

Enumeração Descrição do motivo do fechamento do formulário para cada valor
ApplicationExitCall O fechamento esta sendo feito via chamada a Application.Exit()
MdiFormClosing O formulário MDI do formulário esta sendo fechado
None causas desconhecidas
FormOwnerClosing O formulário proprietário esta sendo fechado
TaskManagerClosing O administrador de tarefas do Windows esta fechando o formulário
UserClosing O fechamento esta sendo feito via Close ou clicando no X do menu de controle
WindowsShutDown O Windows esta sendo encerrado

Se o controle backgroundworker estiver realizando uma operação assíncrona ou operação em background cancelamos a operação.

A seguir vemos o projeto em execução:

Pegue o projeto completo aqui:  BackGroundWorkerExemplo.zip

1Ts 4:13 Não queremos, porém, irmãos, que sejais ignorantes acerca dos que já dormem, para que não vos entristeçais como os outros que não têm esperança.

1Ts 4:14 Porque, se cremos que Jesus morreu e ressurgiu, assim também aos que dormem, Deus, mediante Jesus, os tornará a trazer juntamente com ele.

Referências:


José Carlos Macoratti