Capturando a tela em Windows Forms

Olá pessoal,

Hoje vou demonstrar como é possível capturar a tela ou até mesmo o conteúdo de um controle e salvá-lo como um Bitmap. Imagine que você tem uma solução de atendimento ao cliente e em algum momento precise capturar a tela do seu usuário e depois anexá-la a algum requisito do software ou tratamento de um bug.

Claro que existem várias ferramentas prontas para captura de tela, mas vamos ver como é possível, através de um código em C# usando o recurso de Interop e acessando a API do Windows, criar um método reusável que pode ser utilizado para capturar vários tipos de tela no Windows.

Criando o projeto

Vamos criar um projeto no Visual Studio 2013 do tipo Windows Forms (você pode utilizar qualquer outra versão do Visual Studio):

Para deixar o nosso código de captura reutilizável, vamos criar uma nova classe chamada Tela.cs. Nesta classe iremos criar todo o código de captura, iniciando pelas referências a API do Windows. Esta API é muito rica e possui centenas de métodos muito interessantes, mas vamos nos concentrar basicamente nas rotinas de captura de tela.

Só para conhecimento, todos os controle do Windows são tratados como uma janela e como tal, possuem um identificador único de janela, ou um WindowsHandle. Para acessarmos qualquer informação de uma janela ou controle usando a API do Windows, precisamos deste identificador.

Trabalhando com Interop

Para começar a nossa classe, vamos colocar todo o código de Interop com a API do Windows. Eu não vou explicar em detalhes o que cada método faz, mas você pode acessar um site muito bom chamado PInvoke.net que contém referências, explicações e exemplos para a API do Windows.

O código abaixo faz uma referência para um método da API do Windows e o deixa acessível para nós no C#:

public class Tela
{
  [DllImport("user32.dll", EntryPoint = "GetDC")]
  static extern IntPtr GetDC(IntPtr ptr);
  [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
  static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
  [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
  static extern IntPtr DeleteDC(IntPtr hDc);
  [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
  static extern IntPtr CreateCompatibleDC(IntPtr hdc);
  [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
  static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
  [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
  static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
  [DllImport("gdi32.dll", EntryPoint = "BitBlt")]
  static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp);
  [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
  static extern IntPtr GetDesktopWindow();
  [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
  static extern IntPtr DeleteObject(IntPtr hDc);

  const int SRCCOPY = 13369376;
  public struct SIZE
  {
      public int cx;
      public int cy;
  }
}

Entendo um pouco a API do Windows

DC – Device Context, ou Dispositovo de Contexto, é um identificador para um objeto no Windows, por exemplo uma janela ou controle.

GetDC() – devolve o identificador para uma janela.

ReleaseDC() – restaura o identificador para o Windows. Isto é muito importante, todo identificador que for utilizado, deve ser devolvido.

DeleteDC() – quando nós criamos o DC, ao invés de pegá-lo do Windows, precisamos deletá-lo para liberar o recurso.

CreateCompatibleDC() – cria um DC, no nosso caso iremos criar para manipular o Bitmap.

CreateCompatibleBitmap() – cria um Bitmap compatível com um DC.

SelectObjec() – seleciona um objeto, no nosso caso vincula o DC ao Bitmap.

BitBlt() – faz a cópia do conteúdo de um identificador para outro, e no nosso caso, o identificador de destino será um Bitmap.

GetDesktopWindow() – retorna um identificador para a janela principal do Windows (Desktop).

DeleteObject() – deleta um objeto, liberando o recurso alocado.

Criando a rotina que captura a tela

Agora que já entendemos um pouco da API do Window, vamos criar o método da nossa classe que irá fazer as capturas de tela. Para isto vamos adicionar o código abaixo a nossa classe Tela.cs:

public static Bitmap RetornaImagemControle(IntPtr controle, Rectangle area)
{
    SIZE size;
    IntPtr hBitmap;

    IntPtr hDC = GetDC(controle);
    IntPtr hMemDC = CreateCompatibleDC(hDC);

    size.cx = area.Width - area.Left;
    size.cy = area.Bottom - area.Top;

    hBitmap = CreateCompatibleBitmap(hDC, size.cx, size.cy);

    if (hBitmap != IntPtr.Zero)
    {
        IntPtr hOld = (IntPtr)SelectObject(hMemDC, hBitmap);
        BitBlt(hMemDC, 0, 0, size.cx, size.cy, hDC, 0, 0, SRCCOPY);
        SelectObject(hMemDC, hOld);
        DeleteDC(hMemDC);
        ReleaseDC(GetDesktopWindow(), hDC);
        Bitmap bmp = System.Drawing.Image.FromHbitmap(hBitmap);
        DeleteObject(hBitmap);
        return bmp;
    }
    else
    {
        return null;
    }
}

O nosso método recebe como parâmetro um IntPtr, que pode representar qualquer controle ou janela do Windows, e recebe também uma estrutura que representa a área que iremos capturar, pois poderemos querer apenas uma parte do nosso controle transformado em uma imagem.

Iniciamos pegando os identificadores necessários (linhas 6 e 7) e em seguida calculamos o tamanho do nosso Bitmap de captura (linhas 9 e 10), para depois já criarmos este Bitmap (linha 12). Caso consigamos criar o Bitmap, então vamos fazer o vínculo do Bitmap (linha 16), e finalmente iremos executar o método que faz toda a mágica, ou seja, que copia o conteúdo do controle para o nosso Bitmap (linha 17).

Após isto iremos liberar os recursos utilizados (linhas 19 e 20), criamos finalmente o nosso Bitmap (linha 21) e liberamos o último recurso alocado. Ufa! Agora é só retornar o Bitmap com o resultado.

Utilizando a classe para capturar uma informação:

Agora que já temos a classe, vamos voltar para o nosso Windows Forms e mostrar como ele irá funcionar. Para isto adicione um controle Button e também um PictureBox, de maneira que a tela fique parecida com a seguinte:

Vamos adicionar o código para o botão “Capturar Desktop”:

private void btnCapturaDesktop_Click(object sender, EventArgs e)
{
    imagemCapurada.Image = Tela.RetornaImagemControle(IntPtr.Zero, Screen.PrimaryScreen.WorkingArea);
}

Este código tem um truque bem interessante. Como eu falei no início deste post, iríamos capturar uma tela e toda tela no Windows possui um identificador, sendo assim, o nosso Desktop tem a identificação ZERO, ou seja, informando IntPtr.Zero como o nosso controle a ser capturado, iremos capturar o Desktop do Windows, e para conseguirmos capturar toda a tela, eu utilizei a propriedade PrimaryScreen.WorkingArea, que retorna o tamanho do nosso Desktop.

Para finalizar, iremos implementar um outro botão que irá capturar a nossa própria tela, para isto adicione um novo botão ao formulário e acrescente o código a seguir:

private void btnCapturaTela_Click(object sender, EventArgs e)
{
    imagemCapurada.Image = Tela.RetornaImagemControle(this.Handle, new Rectangle(0,0,this.Width,this.Height));
}

Veja que agora estamos informando o identificador do nosso form e também o seu tamanho, mas você pode informar o identificador de qualquer controle e qualquer tamanho, e talvez capturar uma parte do controle original.

Veja como fica a tela capturada:

Conclusão:

Este post mostra duas coisas bem interessantes: como interoperar com a API do Windows e como capturar o conteúdo de uma janela do Windows.

Espero que tenham gostado e que principalmente seja útil no seu dia a dia.

Um grande abraço e até a próxima.

Carlos dos Santos.