Muitas vezes uma aplicação é requerida para fazer operações que consomem tempo no start-up, ou no início do carregamento do sistema. Algumas vezes é necessário se ler dados de um banco de dados, outras pode-se precisar guardar informações de um serviço web. Quando isto acontece, é prático mostrar uma Splash Screen para o usuário, com um logo de uma empresa ou algo do tipo, juntamente com uma barra de progresso indicando quanto tempo o aplicativo vai demorar para começar a rodar. Pode parecer simples a primeira vista, mas isso pode ser algo complicado; se é simplesmente mostrado uma tela, e as operações que consomem tempo continuarem a rodar, a UI vai parar e a barra de progresso nunca será atualizada. Deste

modo, tem-se alguns perigos envolvidos, alguns aspectos a que se ater a atenção, então, será demonstrado um exemplo simples aqui.

Começando pela criação de um simples Windows Forms Project. Assim que estiver carregado, adicione outra janela e chame-a de "SplashScreen". Para que tudo dê certo, selecione as propriedades a seguir:

  • FormBorderStyle: None
  • TopMost: True
  • StartPosition: CenterScreen

Agora, na janela das propriedades, há de se encontrar a propriedade BackgroundImage, clicar na pequena chave {...} e selecionar uma figura do seu disco. A propriedade BackgroundimageLayout está definida como None, mas pode-se fazer o que quiser, neste caso. Então, adiciona-se uma barra de progresso à parte de baixo do form, e muda-se a propriedade Dock para Bottom.

Agora, precisa-se de dar autorização a algo fora desta classe para atualizar o progresso (o progressBar é um membro privado e não pode ser acessado). Aqui está o problema, se simplesmente amarrarmos o valor da propriedade do progress bar no nosso getter/setter, ficaríamos com algo assim:

Listagem 1: Código mostrando o atamento do valor da propriedade do progress bar

public int Progress
{
      get
      {
          return this.progressBar1.Value;
      }
      set
      {
          this.progressBar1.Value = value;
      }
}

Enquanto se faz isso, há de se lembrar que esta splash screen será mostrada numa thread separada. Se se tentar acessar esta propriedade pela thread principal, será mostrada uma InvalidOperationException que diz: "Operações entre threads não-válidas: controle do 'progessBar1' acessado de um thread que não o qual ele foi criado". Então, para poder configurar qualquer um dos elementos UI a partir de outra thread, tem-se que chamar o método Invoke do form que tem um delegate.

Aqui está o código completo para a classe do SplashScreen:

Listagem 2: Código mostrando a SplashScreenclass

using System.Windows.Forms;
 
namespace SplashScreenTesting
{
   public partial class SplashScreen : Form
   {
       private delegate void ProgressDelegate(int progress);
 
       private ProgressDelegate del;
       public SplashScreen()
       {
           InitializeComponent();
           this.progressBar1.Maximum = 100;
           del = this.UpdateProgressInternal;
       }
 
       private void UpdateProgressInternal(int progress)
       {
           if (this.Handle == null)
           {
               return;
           }
           this.progressBar1.Value = progress;
       }
       public void UpdateProgress(int progress)
       {
           this.Invoke(del, progress);
       }
   }
}

Como pode-se ver, criou-se um delegate que será usado para invocar o update da barra de progresso. Vamos criar uma classe que estimula o consumo de tempo. Basicamente, ela somente calcula Math.Pow para os números de um a cem elevados da primeira a 500.000 potência. Toda a vez que nos movemos para outro número (os números de um a cem), nós temos um evento que reporta progresso. Quando está feito, temos um evento que anuncia o fim. Aqui está a classe completa:

Listagem 3: Código mostrando a classe completa

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SplashScreenTesting
{
 public class Hardworker
 {
     public event EventHandler<HardWorkerEventArgs> ProgressChanged;
     public event EventHandler HardWorkDone;
     public void DoHardWork()
     {
         for (int i = 1; i <= 100; i++)
         {
             for (int j = 1; j <= 500000; j++)
             {
                 Math.Pow(i, j);
             }
             this.OnProgressChanged(i);
         }
         this.OnHardWorkDone();
     }
     private void OnProgressChanged(int progress)
     {
         var handler = this.ProgressChanged;
         if (handler != null)
         {
             handler(this, new HardWorkerEventArgs(progress));
         }
     }
     private void OnHardWorkDone()
     {
         var handler = this.HardWorkDone;
         if (handler != null)
         {
             handler(this, EventArgs.Empty);
         }
     }
 }
 public class HardWorkerEventArgs : EventArgs
 {
     public HardWorkerEventArgs(int progress)
     {
         this.Progress = progress;
     }
     public int Progress
     {
         get;
         private set;
     }
 }
}

Agora, a parte principal do aplicativo, a exibição da Splash Screen: no evento load do Form1, criaremos outra thread para realmente mostrar a splash screen e, então, na thread principal nós faremos o "trabalho pesado". Uma vez que o mecanismo que faz o trabalho pesado reportar que o progresso está completo, eliminaremos a splash screen e mostraremos o Form principal.

Listagem 4: código para mostrar a Splash Screen

using System;
using System.Windows.Forms;
using System.Threading;
namespace SplashScreenTesting
{
public partial class Form1 : Form
{
    private SplashScreen splashScreen;
    private bool done = false;
    public Form1()
    {
        InitializeComponent();
        this.Load += new EventHandler(HandleFormLoad);
        this.splashScreen = new SplashScreen();
    }
    private void HandleFormLoad(object sender, EventArgs e)
    {
        this.Hide();
        Thread thread = new Thread(new ThreadStart(this.ShowSplashScreen));
        thread.Start();
        Hardworker worker = new Hardworker();
        worker.ProgressChanged += (o, ex) =>
        {
            this.splashScreen.UpdateProgress(ex.Progress);
        };
        worker.HardWorkDone += (o, ex) =>
        {
            done = true;
            this.Show();
        };
        worker.DoHardWork();
    }
    private void ShowSplashScreen()
    {
        splashScreen.Show();
        while (!done)
        {
            Application.DoEvents();
        }
        splashScreen.Close();
        this.splashScreen.Dispose();
    }
}
}

No construtor, nós somente trabalharemos com o evento Load e renovaremos o SplashScreen. Então, no load do manipulador do evento, escondemos o form corrente, porque não queremos que seja visto ainda. Então criamos um thread e passamos um delegate para o nosso método ShowSplashScreen. O método de mostrar a Splash Screen é o que realmente vai rodar numa thread

separado. Primeiro, é mostrado é SplashScreen. Depois, só é efetuado um loop constante esperando para que o boleano "done" seja mudado para verdadeiro. O elemento chave aqui é a chamada a Application.Doevents().

Quando se roda um Windows Form, é criado um novo form, que então espera por eventos a manipular. Cada vez que o form manipula um evento, ele processa todo o código associado ao evento. Todos os outrros eventos esperam na fila. Enquanto o código manipula o evento, sua aplicação não responde. Por exemplo, o windows não repinta se outra janela é trazida para cima.

Se é chamado DoEvents no código, a aplicação pode manipular os outros eventos. Por exemplo, se tem-se um form que adiciona dados ao aListBox e adiciona DoEvents ao código, o form é pintado novamente quando outra janela é arrasta sobre ela. Se o DoEvents é removido do código, o form não será pintado novamente até que a manipulação do evento iniciada pelo clique seja finalizada.

Basicamente, ele permite permite que outros eventos sejam tratados, apesar de a corrente aplicação estar bloqueando a execução. Isto permite que a nossa barra de progresso seja completada. Voltando ao load do Form, pode-se renovar o HardWorker e acessar seus eventos. Basicamente, toda a vez que o HardWorker reportar progresso, será atualizada a barra de progresso. Então, quando estiver feito, podemos mudar nossa bandeira de "done" para true e mostrar o form principal.

Finalmente, na verdade nós arrembentamos quando chamamos o DoHardWork().

Conclusão

Teste este projeto e rode-o. É na verdade elegante de vê-lo em ação. Este é obviamente um exemplo muito tosco, mas deve dar uma ideia básica de como fazer esse projeto. Espero que tenha gostado de ler este tutorial, até o próximo artigo!