Desenvolvimento - C#

Eventos: Um exemplo prático

Nesse artigo, veremos como efetuar a implementação de um evento que indique o progresso de um processamento efetuado dentro de uma classe.

por Leandro Alves Santos



Nesse artigo, veremos como efetuar a implementação de um evento que indique o progresso de um processamento efetuado dentro de uma classe.

O que é um evento?

Um evento é uma mensagem disparada por um objeto indicando alguma ação executada. Por exemplo, temos o evento Click de um objeto Button que é disparado sempre que um usuário pressiona e solta esse objeto, ou o evento Shown de um objeto Form, que indica que o Form está sendo mostrado pela primeira vez.  Da mesma forma que podemos responder a esses eventos, podemos criar nossos próprios eventos e o usuário de nossa classe pode respondê-los.

Projeto que será criado

Imagine um caso em que o usuário seleciona vários arquivos de texto e clica em um botão em que os arquivos selecionados serão processados. Dependendo do tamanho de cada arquivo, da quantidade de arquivos selecionados, do computador do usuário, etc., essa tarefa pode demorar. Com a tela do aplicativo parada, o usuário pode imaginar que o aplicativo travou e pode tentar fechá-lo ou tentar reiniciar o computador.

Isso pode ser evitado se, durante o processamento dos arquivos, nós informarmos ao usuário o que está ocorrendo internamente através do formulário da aplicação.

Então, vamos criar um aplicativo que simula o processamento de arquivos e indica ao usuário o status desse processamento.

Vamos ao exemplo prático?

Escrevendo a classe que dispara o evento

Crie um projeto no Visual Studio do tipo Class Library com o nome de Arquivos e adicione uma classe ao projeto com o nome de Arquivos como podemos ver nas Figuras 1 e 2. O escopo dessa classe será public. Veja a declaração da classe na Listagem 1.


Figura 1 – Criando o projeto Arquivos


Figura 2 – Adicionando a classe Arquivos ao nosso projeto

namespace Arquivos

{

   public class Arquivos

   {

   }

}

Listagem 1 – Declaração da classe Arquivos

Na Listagem 2, vamos declarar um membro da classe do tipo StringCollection que conterá o nome dos arquivos a serem processados e no construtor da classe, vamos preencher esse objeto com nomes de arquivos para fazermos a simulação.

System.Collections.Specialized.StringCollection arquivos;

public Arquivos()

{

   //Adicionamos algumas strings na coleção.

   //Essas strings contém os nomes dos arquivos

   //que serão processados (Simulação).

   arquivos = new System.Collections.Specialized.StringCollection();

   for (Int32 i = 1; i <= 5; i++)

      arquivos.Add("arquivo " + i + ".txt");

}

Listagem 2 – Código criado para gerar a simulação

Também teremos nessa classe, uma propriedade que retorna o número de arquivos a serem processados. Isso é mostrado na Listagem 3.

//Propriedade que retorna a quantidade de arquivos que serão processados

public Int32 QuantidadeArquivos

{

   get { return arquivos.Count; }

}

Listagem 3 – Propriedade que retorna a quantidade de arquivos

Na Listagem 4, criamos o método que simula o processamento dos arquivos, efetuamos um looping no objeto arquivos, percorrendo cada string e incluimos uma chamada ao método Sleep(), simulando um tempo de processamento em cada arquivo.

//Método que simula a importação de arquivos.

public void CarregarArquivos()

{

   foreach (String arquivo in arquivos)

   {

  

      //O método sleep faz a aplicação parar de executar durante um              

      //determinado tempo

      //Colocamos esse comando para simular o processamento dos

      //arquivos

      System.Threading.Thread.Sleep(1000);               

   }

}

Listagem 4 – Primeira versão do método de processamento

Antes de escrever o código do nosso evento, vamos criar a classe que conterá os dados do nosso evento. Essa classe herda da classe EventArgs.

Essa classe conterá uma variável que vamos utilizar para passar o nome do arquivo que será processado para a classe que vai responder ao nosso evento. Essa variável será privada e incluíremos uma propriedade para acessá-la.

Notem na Listagem 5 que a nossa propriedade contém o método set declarado com o escopo internal. Quando declaramos um membro como internal, impedimos que esse membro seja acessado fora do assembly em que a classe foi criada. Fizemos isso para impedir que o método que responde ao nosso evento possa modificá-la.

//Essa classe contém os argumentos do nosso evento.

//Nesse caso específico o único argumento que temos

//é o nome do arquivo que será processado.

public class CarregandoArquivoEventArgs : EventArgs

{

   private string _nomeArquivo;

   public String NomeArquivo

   {

      get

      {

         return this._nomeArquivo;

      }

      //Set está com o escopo internal, pois

      //o mesmo só pode ser acessado dentro desse assembly.

      //Dessa forma, no nosso formulário a propriedade NomeArquivo

      //será somente leitura.

      internal set

      {

         this._nomeArquivo = value;

      }

   }

}

Listagem 5 – Classe que contém os dados do evento

Agora vamos declarar o delegate e o evento da classe. Veja na Listagem 6 que o delegate contém uma assinatura, seu retorno é void, e os parâmetros são do tipo object e CarregandoArquivoEventArgs. No parâmetro do tipo object, passaremos uma referência da classe que disparou o evento e no parâmetro do tipo CarregandoArquivoEventArgs, passaremos uma referência a um objeto criado na nossa classe e que conterá o nome do arquivo que será processado.

Mas o que é um delegate?

Para capturarmos um evento e efetuarmos alguma ação com base nesse evento, precisamos de um delegate. Um delegate é uma classe que contém uma referência para um método. Basicamente, um delegate nesse caso, é utilizado como uma ponte entre o objeto que disparou o evento e o objeto que tratará esse evento.

public delegate void CarregandoArquivoDelegate

   (object sender, CarregandoArquivoEventArgs e);

public event CarregandoArquivoDelegate CarregandoArquivo;

Listagem 6 – Declaração do delegate e do evento na classe Arquivos

Estamos quase finalizando a classe arquivo. Agora teremos que revisitar o método CarregarArquivos() para disparar o evento quando um arquivo estiver prestes a ser processado. Veja a nova versão do método na Listagem 7. Primeiro criamos um objeto do tipo CarregandoArquivoEventArgs, e atribuimos a string com o nome do arquivo a propriedade NomeArquivo do objeto. Depois verificamos se o evento é nulo e caso ele não seja nulo, disparamos o evento passando o objeto que está disparando o evento e o objeto do tipo CarregandoArquivoEventArgs que contém o nome do arquivo.

Por que tenho que verificar se um evento é nulo?

Caso o usuário de sua classe não tenha criado nenhum método que responda ao evento, o evento será nulo e quando tentarmos dispará-lo, ocorrerá uma exceção NullReferenceException.

//Método que simula a importação de arquivos.

//Esse método dispara um evento cada vez que vai

//iniciar o processamento de um arquivo

public void CarregarArquivos()

{

   CarregandoArquivoEventArgs e;

   foreach (String arquivo in arquivos)

   {

      e = new CarregandoArquivoEventArgs();

      e.NomeArquivo = arquivo;

      if (CarregandoArquivo != null)

         CarregandoArquivo(this, e);

      //O método sleep faz a aplicação parar de

      //executar durante um determinado tempo

      //Colocamos esse comando para simular o

      //processamento dos arquivos

      System.Threading.Thread.Sleep(1000);               

   }

}

Listagem 7 – Segunda versão do método de processamento

Criando o formulário que vai capturar o evento

Adicione a solução um projeto Windows Forms Application (figura 3), clique nesse projeto com o botão direito e clique em Set as StartUp Project.

Figura 3 – Criando o projeto ProcessadorArquivo

Para trabalharmos com a classe Arquivos, precisamos adicionar uma referência ao projeto Arquivos. Podemos fazer isso clicando com o botão direito no nosso projeto do tipo Windows Forms Application -> Add Reference. Após isso, clique na Aba Projects, selecione o projeto Arquivos e clique em OK (figura 4).

Figura 4 – Referenciando o projeto Arquivos

Inclua os seguintes componentes ao form:

Tipo: Button                       

Propriedades:

Name – btnProcessarArquivos

Text – Processar

Tipo: Label                           

Propriedades:

Name – lblNomeArquivo

Text - <vazio>

Tipo: ProgressBar              

Propriedades:

Name – pbProgresso

O seu form deve ficar semelhante ao da figura 5.

Figura 5 – Formulário do nosso aplicativo

Na Listagem 8, adicionamos um método de captura ao evento Click do botão btnProcessarArquivos. Você pode adicioná-lo clicando duas vezes no botão. Nesse evento,  configuramos a propriedade TopMost para true para que o form fique sempre à frente dos outros forms que não estejam configurados da mesma forma. Desabilitamos o botão para que o usuário veja que o mesmo não pode ser pressionado. Criamos uma instância da nossa classe Arquivos e configuramos a propriedade Maximum do nosso objeto do tipo ProgressBar com a quantidade de arquivos que temos para processar.

Veja a seguinte linha de código:

arquivos.CarregandoArquivo += new

   Arquivos.Arquivos.CarregandoArquivoHandler(

   arquivos_CarregandoArquivo);

Com ela indicamos que o método arquivos_CarregandoArquivo responderá ao evento quando ele for disparado e o seu código será executado.

Para finalizar, chamamos o método que processa os arquivos e ao final de todo o processamento, resetamos o valor do ProgressBar e da Label, habilitamos o botão e configuramos TopMost para false.

private void btnProcessarArquivos_Click(object sender, EventArgs e)

{

   this.TopMost = true;

   //Desabilitamos o botão.

   btnProcessarArquivos.Enabled = false;

   //Configuramos a propriedade Maximum da nossa

   //barra de progresso com o número de arquivos.

   Arquivos.Arquivos arquivos = new Arquivos.Arquivos();

   pbProgresso.Maximum = arquivos.QuantidadeArquivos;

   //Adicionamos um "handler" do evento que é

   //disparado pela classe arquivo

   //toda vez que um arquivo vai ser importado

   arquivos.CarregandoArquivo += new

      Arquivos.Arquivos.CarregandoArquivoDelegate(

      arquivos_CarregandoArquivo);

   //Essa chamada inicia o processamento dos arquivos

   arquivos.CarregarArquivos();

   //Mostramos uma mensagem indicando o fim do processamento

   MessageBox.Show("Arquivos carregados com sucesso.");

   //Resetamos o valor do ProgressBar e da Label

   pbProgresso.Value = 0;

   lblNomeArquivo.Text = String.Empty;

   //Habilitamos o botão.

   btnProcessarArquivos.Enabled = true;

   this.TopMost = false;

}

Listagem 8 – Código do evento Click do botão btnProcessar

Agora precisamos escrever o código do método que responderá ao evento. Podemos ver o código na Listagem 9. Esse método atualiza a label com o nome do arquivo que será processado e incrementa o valor da barra de progresso. Ao final disso o form é atualizado para que possamos visualizar os novos valores dos objetos.

void arquivos_CarregandoArquivo(object sender, Arquivos.CarregandoArquivoEventArgs e)

{

   //Quando um arquivo está prestes a ser importado,

   //um evento é disparado e o capturamos aqui.

   //Alteramos o label com o nome de arquivo que será importado,

   //incrementamos o valor da barra de progresso e atualizamos

   //o form

   lblNomeArquivo.Text = e.NomeArquivo;

   pbProgresso.Value++;

   this.Update();           

}  

Listagem 9 – Método que responde ao evento CarregandoArquivo

Execute o aplicativo e você verá o progresso da nossa simulação, tanto na Label que indica qual arquivo está sendo processado, como na barra de progresso.

Figura 6 – Progresso do processamento sendo mostrado no formulário

Mas o meu form fica travado. E agora?

É possível deixar o form livre para que o usuário continue fazendo outras tarefas durante o processamento dos arquivos através de threads e com a utilização do método Invoke para a atualização dos objetos do formulário quando um arquivo começa a ser processado, mas isso foge do escopo do artigo.

Conclusão

Com poucas linhas de código podemos criar um evento para o nosso aplicativo enviar respostas durante o processamento para que o usuário não fique com a impressão de que o aplicativo travou e pare o processo forçando o seu encerramento.

Leandro Alves Santos

Leandro Alves Santos - Visite o blog do autor: http://weblogs.pontonetpt.com/las/.