Capturando a tela em Windows Forms

Carlos dos Santos

Dn630211.060DE5057573180CEC6D227C6D3E2207(pt-br,MSDN.10).png

Março, 2014

Este artigo 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):

Dn630211.12BA2209BC8CBE9714CBD698DFDF3D8D(pt-br,MSDN.10).png

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 www.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#:

   1: public class Tela
   2:     {
   3:         [DllImport("user32.dll", EntryPoint = "GetDC")]
   4:         static extern IntPtr GetDC(IntPtr ptr);
   5:         [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
   6:         static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
   7:         [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
   8:         static extern IntPtr DeleteDC(IntPtr hDc);
   9:         [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
  10:         static extern IntPtr CreateCompatibleDC(IntPtr hdc);
  11:         [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
  12:         static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
  13:         [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
  14:         static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
  15:         [DllImport("gdi32.dll", EntryPoint = "BitBlt")]
  16:         static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp);
  17:         [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
  18:         static extern IntPtr GetDesktopWindow();
  19:         [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
  20:         static extern IntPtr DeleteObject(IntPtr hDc);
  21:
  22:         const int SRCCOPY = 13369376;
  23:         public struct SIZE
  24:         {
  25:             public int cx;
  26:             public int cy;
  27:         }

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:

   1: public static Bitmap RetornaImagemControle(IntPtr controle, Rectangle area)
   2:         {
   3:             SIZE size;
   4:             IntPtr hBitmap;
   5:
   6:             IntPtr hDC = GetDC(controle);
   7:             IntPtr hMemDC = CreateCompatibleDC(hDC);
   8:
   9:             size.cx = area.Width - area.Left;
  10:             size.cy = area.Bottom - area.Top;
  11:
  12:             hBitmap = CreateCompatibleBitmap(hDC, size.cx, size.cy);
  13:
  14:             if (hBitmap != IntPtr.Zero)
  15:             {
  16:                 IntPtr hOld = (IntPtr)SelectObject(hMemDC, hBitmap);
  17:                 BitBlt(hMemDC, 0, 0, size.cx, size.cy, hDC, 0, 0, SRCCOPY);
  18:                 SelectObject(hMemDC, hOld);
  19:                 DeleteDC(hMemDC);
  20:                 ReleaseDC(GetDesktopWindow(), hDC);
  21:                 Bitmap bmp = System.Drawing.Image.FromHbitmap(hBitmap);
  22:                 DeleteObject(hBitmap);
  23:                 return bmp;
  24:             }
  25:             else
  26:             {
  27:                 return null;
  28:             }
  29:         }

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:

Dn630211.B437E2172C4B3D20111A7C41569D7627(pt-br,MSDN.10).png

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

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

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:

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

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:

Dn630211.C46A5E3A588CFC5D72C0039586856118(pt-br,MSDN.10).png

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.

| Home | Artigos Técnicos | Comunidade